Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent accurate comparison of floating-point numbers #38

Open
wants to merge 1 commit into
base: 1.4.x
Choose a base branch
from

Conversation

dmytro-dymarchuk
Copy link

@dmytro-dymarchuk dmytro-dymarchuk commented Oct 1, 2018

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 dmytro-dymarchuk force-pushed the prevent-accurate-comparison-of-floating-point-numbers branch 2 times, most recently from 460df80 to a9f590b Compare October 1, 2018 17:47
@dmytro-dymarchuk dmytro-dymarchuk force-pushed the prevent-accurate-comparison-of-floating-point-numbers branch from a9f590b to 4f11909 Compare October 1, 2018 18:57
@dmytro-dymarchuk dmytro-dymarchuk force-pushed the prevent-accurate-comparison-of-floating-point-numbers branch from 4f11909 to 07e5b84 Compare October 1, 2018 19:14
@JanTvrdik
Copy link

JanTvrdik commented Oct 2, 2018

Just a note: abs($left - $right) < $epsilon does not always work. See for example nette/tester#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.

@jasny
Copy link

jasny commented Nov 20, 2018

@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

@adaamz
Copy link
Contributor

adaamz commented Feb 12, 2019

@Majkl578
Copy link
Contributor

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

@adaamz
Copy link
Contributor

adaamz commented Feb 12, 2019

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

@ondrejmirtes
Copy link
Member

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.

@Majkl578
Copy link
Contributor

Majkl578 commented Aug 3, 2020

@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

@ondrejmirtes
Copy link
Member

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

@VincentLanglet
Copy link
Contributor

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
Copy link
Contributor

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 ?

@dereuromark
Copy link
Contributor

dereuromark commented Nov 23, 2023

$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

@TomAdam
Copy link

TomAdam commented Nov 23, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants