5

I am in the process of upgrading our code from php 8.2 to 8.4

I noticed we are getting some test failures because of round() returning different values than expected. Ultimately the problem could be tracked down to rounding the result of certain multiplications giving different results in some cases than under 8.2. For example, round(365 * 0.7) now results in 255 whereas in 8.2 it results in 256.

You can see the difference here: https://3v4l.org/iT6T2#v8.4.14

In both versions, the result of 365 * 0.7 is apparently 255.49999999999997 so the issue must be down to the round().

I don't understand why this happens, can anyone enlighten me?

0

3 Answers 3

5

It looks like there were 2 commits some time last year that changed the rounding function. This commit on Feb 3, 2024 for bug fix GH-12143 has this explanation:

Fixed a bug caused by "pre-rounding" of the round() function. Previously, using "pre-rounding" to treat a value like 0.285 (actually 0.28499999999999998) as a decimal number and round it to 0.29. However, "pre-rounding" incorrectly rounds certain numbers, so this fix removes "pre-rounding" and changes the way numbers are compared, so that the values ​​are correctly rounded as decimal numbers.

Since it's now rounding .5 as down instead of up, this might be worthy of adding another bug request.

Sign up to request clarification or add additional context in comments.

5 Comments

I'd only add that it looks like new behavior is correct 255.4(9) should be rounded to 255
Re “Since it's now rounding .5 as down instead of up”: The data in the question does not indicate that. The question notes that 365 * 0.7 produces 255.49999999999997 (actually 255.499999999999971578290569595992565155029296875) and says that PHP 8.4 rounds that to 255. The commit you found says pre-rounding is no longer performed, so we are seeing 255.499999999999971578290569595992565155029296875 directly rounded down. We are not seeing 255.5 rounded down.
Interesting. When I checked the calculator on my phone, the calc function on the command line, and Google, 365 * .7 came out to exactly 255.5. So that's the number I was going off of.
So part of it goes back to Is floating-point math broken? since the rounding is correct for the real math, but the coding math is wonky
@aynber: Yes, sort of, perhaps better than "wonky" is to write that floating point numbers in PHP are imprecise. When you do math with them, the imprecision is always in the last place. So the correct rounding, that is the rounding with the imprecision, then has to yield 255 (which is imprecise) and the imprecision remains in the last place. So that in itself likely is not worth adding another bug report, but additionally PHP originally rounded to 255 already in the past until a regression was introduced, I've also added an answer.
3

The "error" isn't in the round, it's in the multiplication. 365 * 7 is 2555, so 365 * 0.7 has to be 255.5, which means that the round() result should be 256, assuming you're using PHP_ROUND_HALF_UP.

However, in PHP 8+:

var_dump(365 * .7);
float(255.49999999999997)

While in all prior versions:

var_dump(365 * .7);
float(255.5)

<shrug> Because floating point.

Interestingly though:

var_dump(365 * 7 / 10);
float(255.5)

So, you might adjust your test to use 365 * 7 / 10 instead of 365 * .7, but that will likely just turn into a never-ending cat and mouse game. You've essentially got two choices:

First, don't build unit tests based on exact values of floating point calculations. Update your test so that, instead of expecting an exact match, it expects a value within a certain margin of error. For example, PHPUnit has assertEqualsWithDelta():

$this->assertEqualsWithDelta(255.5, 365 * .7, .1); 

Second, if you really need to care about the difference between 255.49999999999997 and 255.5 then you're going to have to choose how many digits you care about, and then use BCMath:

var_dump(bcmul('365', '0.7', 2));
string(6) "255.50"

Comments

0

This was a long standing regression introduced with PHP 5.2.7. PHP 4 and 5 versions prior to it rounded to 255.

PHP version 8.4 has finally fixed the round() return value. Better late than never I'd say.

Note that with floating point numbers in PHP, the imprecision is always in the last place. This is per the standard.

As Alex has it in their answer, this is why in testing you should use some delta value as PhpUnit has it since literally ages.

See as well the Fuzzy Equality Assertion variation of the implementation strategy for the Assertion Method.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.