phpstan-strict-rules icon indicating copy to clipboard operation
phpstan-strict-rules copied to clipboard

Prevent accurate comparison of floating-point numbers

Open dmytro-dymarchuk opened this issue 5 years ago • 12 comments

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 avatar Oct 01 '18 17:10 dmytro-dymarchuk

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.

JanTvrdik avatar Oct 02 '18 13:10 JanTvrdik

@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

jasny avatar Nov 20 '18 11:11 jasny

btw https://github.com/Roave/no-floaters

adaamz avatar Feb 12 '19 18:02 adaamz

@CZechBoY Forbidding floats altogether and avoiding errorneous comparison by equality are two different use cases.

Majkl578 avatar Feb 12 '19 19:02 Majkl578

@Majkl578 sure, I only send link to similiar library.

adaamz avatar Feb 12 '19 20:02 adaamz

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 avatar Aug 02 '20 12:08 ondrejmirtes

@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

Majkl578 avatar Aug 03 '20 01:08 Majkl578

The linked article shows that $test == 0.3 can be valid code but it would be marked with an error by this PR.

ondrejmirtes avatar Aug 03 '20 08:08 ondrejmirtes

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.

VincentLanglet avatar Jun 29 '21 17:06 VincentLanglet

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 ?

VincentLanglet avatar Oct 22 '22 21:10 VincentLanglet

$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

dereuromark avatar Nov 23 '23 02:11 dereuromark

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.

TomAdam avatar Nov 23 '23 15:11 TomAdam