Why does `intval(19.9 * 100)` equal `1989`? - php

Boy, this one is really weird. I expect the following code to print 1990, but it prints 1989!
$val = '$19.9';
$val = preg_replace('/[^\d.]/','',$val);
$val = intval($val * 100);
echo $val;
Why on earth is this happening?
Edit: and this code:
$val = '$19.9';
$val = preg_replace('/[^\d.]/','',$val);
echo $val . "<br>";
$val = $val * 100;
echo $val . "<br>";
$val = intval($val);
echo $val;
Prints:
19.9
1990
1989
Why does intval(1990) equal 1989???

This is a precision issue inherent to floating point numbers in PHP, and lots of other languages. This bug report discusses it a bit, in the context of casting as an int:
http://bugs.php.net/bug.php?id=33731
Try round($val * 100) instead.

The usual answer to this kind of question is to read What Every Computer Scientist Should Know About Floating-Point Arithmetic.

Why does intval(1990) equal 1989???
Because you're not taking intval(1990). You're taking intval($val * 100) where $val is a number close to, but slightly smaller than, 19.9.
Read The Floating-Point Guide to understand why this is so.
As for how to fix it: don't ever use floating-point values for money. In PHP, you should use BCMath instead.

i was facing similar issue with my code but got solution php.net
need to convert variable to string for intval operation e.g:
intval( 9.62 * 100 ) //gives 961
intval( strval( 9.62 * 100 ) ) //gives 962

$val is a floating point number - the result of "19.9" * 100. Floating point numbers are not 100% accurate in any language (this is by design). If you need 100% decimal accuracy for dollar values, you should use integers and perform all calculations using cents (E.g., "$19.90" should be 1990).

Related

PHP round to integer

I want to round a number and I need a proper integer because I want to use it as an array key. The first "solution" that comes to mind is:
$key = (int)round($number)
However, I am unsure if this will always work. As far as I know (int) just truncates any decimals and since round($number) returns a float with theoretically limited precision, is it possible that round($number) returns something like 7.999999... and then $key is 7 instead of 8?
If this problem actually exists (I don't know how to test for it), how can it be solved? Maybe:
$key = (int)(round($number) + 0.0000000000000000001) // number of zeros chosen arbitrarily
Is there a better solution than this?
To round floats properly, you can use:
ceil($number): round up
round($number, 0): round to the nearest integer
floor($number): round down
Those functions return float, but from Niet the Dark Absol comment: "Integers stored within floats are always accurate, up to around 2^51, which is much more than can be stored in an int anyway."
round(), without a precision set always rounds to the nearest whole number. By default, round rounds to zero decimal places.
So:
$int = 8.998988776636;
round($int) //Will always be 9
$int = 8.344473773737377474;
round($int) //will always be 8
So, if your goal is to use this as a key for an array, this should be fine.
You can, of course, use modes and precision to specify exactly how you want round() to behave. See this.
UPDATE
You might actually be more interested in intval:
echo intval(round(4.7)); //returns int 5
echo intval(round(4.3)); // returns int 4
What about simply adding 1/2 before casting to an int?
eg:
$int = (int) ($float + 0.5);
This should give a predictable result.
Integers stored within floats are always accurate, up to around 253, which is much more than can be stored in an int anyway. I am worrying over nothing.
For My Case, I have to make whole number by float or decimal type
number. By these way i solved my problem. Hope It works For You.
$value1 = "46.2";
$value2 = "46.8";
// If we print by round()
echo round( $value1 ); //return float 46.0
echo round( $value2 ); //return float 47.0
// To Get the integer value
echo intval(round( $value1 )); // return int 46
echo intval(round( $value2 )); // return int 47
My solution:
function money_round(float $val, int $precision = 0): float|int
{
$pow = pow(10, $precision);
$result = (float)(intval((string)($val * $pow)) / $pow);
if (str_contains((string)$result, '.')) {
return (float)(intval((string)($val * $pow)) / $pow);
}
else {
return (int)(intval((string)($val * $pow)) / $pow);
}
}
Round to the nearest integer
$key = round($number, 0);

PHP price to int is losing a cent [duplicate]

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 8 years ago.
I'm utterly confused why PHP is behaving the way it is below.
Context: I'm using a third party payment gateway library. For some reason some of my payments are getting charged 1c less! Which is a huge problem for us. To make things even more odd, it only seems to be for some specific amounts
Looking at their code I was able to reproduce this in a simple php script.
<?php
$val = 568.3 * 100;
echo $val;
echo "\n";
echo (float) ($val);
echo "\n";
echo (int) ($val);
echo "\n";
echo intval($val);
echo "\n";
?>
Expected output would be 56830 for all the echo's but instead, when its casting or using intval it prints out 56829 (1c less) and using no type cast or float works. The fix seems to be just not using int or intval conversions but am very curious why this is happening.
If you put in 56830 then it all prints fine. Reproduceable with 568.31 * 100 but not 568.32 * 100.
Can anyone help me understand whats happening?
EDIT: float / floatval / no casting returns the expected value.
The follow works, just when using 568.3 it loses 1 cent!
$val = 5.3 * 100;
$val = 56888.3 * 100;
FuzzyTree's answer explained the problem with floating point arithmetic. You can fix it by using round before intval.
<?php
$val = 568.3 * 100;
echo $val;
echo "\n";
echo (float) ($val);
echo "\n";
echo (int) (round($val));
echo "\n";
echo intval(round($val));
echo "\n";
?>
Output:
56830
56830
56830
56830
$val is float with a value of something like 56829.999999... and it gets rounded down when converting to an int.
From the manual
http://www.php.net/manual/en/language.types.integer.php
From floating point numbers ΒΆ
When converting from float to integer, the number will be rounded
towards zero.
If the float is beyond the boundaries of integer (usually +/- 2.15e+9
= 2^31 on 32-bit platforms and +/- 9.22e+18 = 2^63 on 64-bit platforms other than Windows), the result is undefined, since the float doesn't
have enough precision to give an exact integer result. No warning, not
even a notice will be issued when this happens!

PHP how to show all decimal places?

this might be a stupid question but I have searched again and again without finding any results.
So, what I want is to show all the decimal places of a number without knowing how many decimal places it will have. Take a look at this small code:
$arrayTest = array(0.123456789, 0.0123456789);
foreach($arrayTest as $output){
$newNumber = $output/1000;
echo $newNumber;
echo "<br>";
}
It gives this output:
0.000123456789
1.23456789E-5
Now, I tried using 'number_format', but I don't think that is a good solution. It determines an exact amount of decimal places, and I do not know the amount of decimal places for every number. Take a look at the below code:
$arrayTest = array(0.123456789, 0.0123456789);
foreach($arrayTest as $output){
$newNumber = $output/1000;
echo number_format($newNumber,13);
echo "<br>";
}
It gives this output:
0.0001234567890
0.0000123456789
Now, as you can see there is an excess 0 in the first number, because number_format forces it to have 13 decimal places.
I would really love some guidance on how to get around this problem. Is there a setting in PHP.ini which determines the amount of decimals?
Thank you very much in advance!
(and feel free to ask if you have any further questions)
It is "impossible" to answer this question properly - because a binary float representation of a decimal number is approximate: "What every computer scientist should know about floating point"
The closest you can come is write yourself a routine that looks at a decimal representation of a number, and compares it to the "exact" value; once the difference becomes "small enough for your purpose", you stop adding more digits.
This routine could then return the "correct number of digits" as a string.
Example:
<?php
$a = 1.234567890;
$b = 0.123456789;
echo returnString($a)."\n";
echo returnString($b)."\n";
function returnString($a) {
// return the value $a as a string
// with enough digits to be "accurate" - that is, the value returned
// matches the value given to 1E-10
// there is a limit of 10 digits to cope with unexpected inputs
// and prevent an infinite loop
$conv_a = 0;
$digits=0;
while(abs($a - $conv_a) > 1e-10) {
$digits = $digits + 1;
$conv_a = 0 + number_format($a, $digits);
if($digits > 10) $conv_a = $a;
}
return $conv_a;
}
?>
Which produces
1.23456789
0.123456789
In the above code I arbitrarily assumed that being right to within 1E-10 was good enough. Obviously you can change this condition to whatever is appropriate for the numbers you encounter - and you could even make it an optional argument of your function.
Play with it - ask questions if this is not clear.

PHP rounding error with simple multiplication [duplicate]

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 3 years ago.
PHP seems to round incorrectly when using (int) to cast variables. Why?
$multiplier = 100000000;
$value = 0.01020637;
echo (int)($value*$multiplier);
Output: 1020636. (unexpected output)
$multiplier = 100000000;
$value = 0.01020637;
echo ($value*$multiplier);
Output: 1020637. (Expected correct output)
Edit: it gets even worse...
$multiplier = 100000000;
$value = 0.01020637;
echo $temp = ($value*$multiplier);
echo '<br/>';
echo (int)$temp;
Output:
1020637
1020636
Things can get hairy when you're dealing with floats, floating point math (and problems involved) are well understood, but can crop up when you're not expecting them. As seems to have happened here. You could read up on the rules extensively, or use language provided tools when handling floating point arithmetic.
When you care about the precision involved you should use the bcmul() function. It's an "optional" extension, but if you care about precision it starts being required rather quickly.
Example:
multiplier = 100000000;
$value = 0.01020637;
echo (int)($value*$multiplier);
echo "\n";
echo bcmul($value, $multiplier, 0);
Sample: http://ideone.com/Wt9kKb
PHP (especially in 32 bit builds) has problems with floating point numbers. This is why casting float into int can have unpredictable results. See PHP Integer page for more detail. Basically, you're getting tiny imprecisions in the math and that can cause serious problems when trying to do something like ceil()
If you really need the numbers converted to int I would suggest you round the numbers first
$multiplier = 100000000;
$value = 0.01020637;
$temp = round($value*$multiplier);
echo $temp . '<br/>' . (int)$temp;
This works by truncating off the small floating point errors. While bcmath can also do the truncation, it's not part of PHP core and not a good overall solution. Your best bet is to write a rounding routine yourself that can return the precision you're looking for. In the project I work on, that was what we did. We wrote our own rounding function and it fixes the problems you'll run into. Without knowing the specifics of what you're trying to do it's hard to say if that's what you need but it's how we did it without bcmath.
The problem you're seeing is the following:
When multiplying two numbers like this:
$mulitply = 0.1 * 100;
You are not multiplying exactly 100 with 0.1, but with with 0.09999999998...
And when it comes to (int), it converts numbers like 4.999 to 4, so your result 1020636.999999999 becomes 1020636 when counting with (int).
bcmul allows for higher precision
$test = (int) bcmul('100000000', '0.01020637');
echo $test
returns the correct answer.
To round floats in PHP you should use the round() function. Just casting to an integer does not round the value correctly.
First argument is which float (the result of your calculation in this case) to be rounded, second is optional, and specifies the amount of decimals (aka precision) being returned. There is also a third argument, controlling the mode. These can be PHP_ROUND_HALF_UP, PHP_ROUND_HALF_DOWN, PHP_ROUND_HALF_EVEN or PHP_ROUND_HALF_ODD.
Example from php.net/round:
<?php
echo round(3.4); // 3
echo round(3.6); // 4
echo round(3.6, 0); // 4
echo round(1.95583, 2); // 1.96
// With the third element, "mode"
echo round(9.5, 0, PHP_ROUND_HALF_UP); // 10
echo round(9.5, 0, PHP_ROUND_HALF_DOWN); // 9
echo round(9.5, 0, PHP_ROUND_HALF_EVEN); // 10
echo round(9.5, 0, PHP_ROUND_HALF_ODD); // 9
?>
An example for your code (live example):
<?php
$multiplier = 100000000;
$value = 0.01020637;
echo intval(round($value*$multiplier)); // Returns 1020637
?>

PHP ceil function strange behavior ?

Can somebody explain this ?
echo ceil( 20.7 * 100 ); // returns 2070
echo ceil( 2070 ); // returns 2070
all OK and logical, but
echo ceil( 40.7 * 100 ); // returns 4071
echo ceil( 4070 ); // returns 4070
not OK and not logical...
Why is this difference ?
Thanks
The wonderful world of floating point numbers:
printf("%.18f\n", 40.7*100);
//prints 4070.000000000000454747
printf("%.18f\n", 20.7*100);
//prints 2070.000000000000000000
In short: floating point numbers cannot represent all rational numbers exactly. In particular, neither 407/10 nor 207/10 can be represented exactly, and so the result of integer conversion always has an uncertainty of one unit.
The only rational numbers which can be represented exactly as binary floating point numbers are of the form "small odd integer times power of two", or in other words, those which have a small binary expansion.
Floating point errors. 40.7 cannot be represented exactly in a float. It'll be something like 40.700000001 or whatever. When you * 100 and ceil it, it rounds up to 4071.
Use arbitrary precision library bcmath e.g.:
ceil(bcmul(40.7, 100)); // 4070
Floating point numbers issue... you can overcome your problem with something like this:
echo ceil( (int) (40.7 * 100) );
You can overcome your problem with something like this:
$result = 40.7 * 100;
$result = (string) $result;
echo ceil($result);

Categories