phpstan-strict-rules
phpstan-strict-rules copied to clipboard
Prevent accurate comparison of floating-point numbers
Prevent situation when comparison return can unexpected result. For example:
php > $a = 0.15 + 0.15;
php > $b = 0.1 + 0.2;
php > $c = 0.3;
php > var_dump($a === $b);
bool(false)
php > var_dump($b == $c);
bool(false)
php > var_dump($b <= $c);
bool(false)
Just a note: abs($left - $right) < $epsilon
does not always work. See for example https://github.com/nette/tester/pull/68
Edit: Although i guess it depends on how you compute $epsilon
. If you scale it with the $left
and $right
it should work just fine.
@ondrejmirtes Here a realistic case where this would cause an issue.
function isPaid(array $amounts, array $payments)
{
$total = array_sum($amounts);
$paid = array_sum($payments);
return $paid >= $total;
}
$amounts = [0.1, 0.2];
$payments = [0.15, 0.15];
var_dump(isPaid($amounts, $payments)); // false
btw https://github.com/Roave/no-floaters
@CZechBoY Forbidding floats altogether and avoiding errorneous comparison by equality are two different use cases.
@Majkl578 sure, I only send link to similiar library.
Hi, I'm not sure about this one, I worry it'd be too annoying. AFAIK floats are precise to a certain number of decimals so for example if you compare two floats after rounding them to one decimal place (like 1.3
) then comparing them with ===
is fine.
But feel free to publish this as a separate package, I'm certain some people would like it. I'm gonna wait some time before closing for feedback from the others.
@ondrejmirtes
AFAIK floats are precise to a certain number of decimals so for example if you compare two floats after rounding them to one decimal place (like 1.3) then comparing them with === is fine.
The (need of / absence of) rounding in first place is the problem here as it's something people usually don't do / forget. E.g.:
php > var_dump(.1 + .2 == .3);
bool(false)
IMO PHPStan should warn about this code, possibly directly in core, not only in phpstan-strict-rules. 🤔
https://andy-carter.com/blog/don-t-trust-php-floating-point-numbers-when-equating
The linked article shows that $test == 0.3
can be valid code but it would be marked with an error by this PR.
The linked article shows that
$test == 0.3
can be valid code but it would be marked with an error by this PR.
Not sure bcadd(0.1, 0.2, 1);
should a real recommended way.
Most of the time you'll sum variable bcadd($a, $b, 1);
, then you don't know how much significative numbers is needed because 0.3001
and 0.3
should not be considered the same.
I find an article recommending (string) $test == '0.3'
to avoid issue with float comparison.
Prevent situation when comparison return can unexpected result. For example:
php > $a = 0.15 + 0.15; php > $b = 0.1 + 0.2; php > $c = 0.3; php > var_dump($a === $b); bool(false) php > var_dump($b == $c); bool(false) php > var_dump($b <= $c); bool(false)
@dmytro-dymarchuk What about the comparison with 0.0
?
Can we have unexpected behavior with code like
$float === 0.0;
$float !== 0.0;
$float >= 0.0;
$float <= 0.0;
Or should it be a special case ignored by your rule ?
$floatAmount1 = 0.1;
$floatAmount2 = 0.2;
$sumFloat = $floatAmount1 + $floatAmount2;
echo "Sum using float: $sumFloat\n"; // Output: Sum using float: 0.30000000000000004
$stringAmount1 = '0.1';
$stringAmount2 = '0.2';
$sumString = bcadd($stringAmount1, $stringAmount2, 1);
echo "Sum using string: $sumString\n"; // Output: Sum using string: 0.3
So yeah, either bcmath and strings, or better yet a value object: https://github.com/php-collective/decimal-object
Having caused myself some serious headaches with float comparisons that work in some situations but not others, I'd be very much behind this being included here. I don't see it as being any more annoying than requiring booleans in conditionals.