php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81601 float - (int) float is sometimes bigger than 1
Submitted: 2021-11-08 19:39 UTC Modified: 2021-12-10 18:03 UTC
From: dktapps at pmmp dot io Assigned:
Status: Not a bug Package: Math related
PHP Version: 8.0.13 OS: Linux
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: dktapps at pmmp dot io
New email:
PHP Version: OS:

 

 [2021-11-08 19:39 UTC] dktapps at pmmp dot io
Description:
------------
I've seen this issue recur many times over the last few years, but never figured out exactly what causes it.

This code: https://github.com/pmmp/PocketMine-MP/blob/3dae87373154ad52ff6c27874f3fd93c9845a8af/src/pocketmine/entity/Human.php#L396
is intended to take a float and split it into its whole-number part (the first argument) and its fractional part (the second argument).

However, sometimes I see crashes like these show in my telemetry:

Value 1.0323429203787 is outside the range 0 - 1

with a trace like this:

#1 pmsrc/src/pocketmine/entity/Human(396): pocketmine\entity\Human->setXpAndProgress(integer 9, double 1.0323429203787)
#2 pmsrc/src/pocketmine/entity/Human(409): pocketmine\entity\Human->setCurrentTotalXp(integer 158)

To the best of my understanding, this should be impossible.

For context, passing 158 into setCurrentTotalXp() causes $newLevel to become float(9.922847983320086), which, when run through the offending code on **my** machine, yields this result:

(int) $newLevel = int(9)
$newLevel - (int) $newLevel = float(0.9228479833200858)

which looks correct.

I have no idea what causes this problem and have never been able to reproduce it firsthand, but such issues have appeared so frequently and across several PHP versions that I believe this is a bug in PHP itself.

Expected result:
----------------
float - (int) float should never be larger than 1.

Actual result:
--------------
float - (int) float is sometimes slightly larger than 1.

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-11-08 20:02 UTC] dktapps at pmmp dot io
I should clarify, this is definitely NOT a bug in JIT because I've seen this issue  in 7.2, 7.3 and 7.4 historically as well.

I also considered the possibility that -ffast-math may be responsible, but I was unable to reproduce the problem even with -ffast-math.

All tests were done on x86_64.
 [2021-11-09 09:58 UTC] nikic@php.net
If float is larger than INT_MAX, then float - (int) float can trivially be larger than 1. However, you seem to be working with numbers that are much smaller than that.

I do wonder why this is not implemented as $float - floor($float) though, that seems like the more natural way to express it, and should avoid edge cases.
 [2021-11-09 12:14 UTC] dktapps at pmmp dot io
I do know of several ways to work around it (fmod(f, 1) being the first thing I thought of), but I thought this should be investigated anyway.
 [2021-11-10 09:39 UTC] php at leinertco dot com
Try to pass $newLevel as 3rd argument in setXpAndProgress, so you have its value in your logs. Maybe this will help :/
 [2021-11-10 13:28 UTC] antonino dot spampinato86 at gmail dot com
Yes, the integer part of the float storage of the implicit integer cast is equivalent to the integer part of the float. But from the source you mentioned I see two numbers with different lengths and I suspect there is a problem with the scientific notation for 0. If you can also print with var_dump(sprintf('%00.53F', $float)); when in source it checks if > 0 so you will have two representations of the number that you can compare for debugging.
 [2021-12-03 12:58 UTC] dktapps at pmmp dot io
-PHP Version: 8.0.12 +PHP Version: 8.0.13
 [2021-12-03 12:58 UTC] dktapps at pmmp dot io
Hi all, this issue recently occurred again despite additional checks in the code specifically designed to trap this edge case. It looks like the value must be getting corrupted somewhere.

Unsuccessful trap: https://github.com/pmmp/PocketMine-MP/blob/6b7d0307afb36a7faca2dd65734e15000baf17ba/src/pocketmine/entity/Human.php#L401
Error thrown here: https://github.com/pmmp/PocketMine-MP/blob/6b7d0307afb36a7faca2dd65734e15000baf17ba/src/pocketmine/entity/Attribute.php#L196
https://crash.pmmp.io/view/5558770
 [2021-12-03 13:01 UTC] dktapps at pmmp dot io
I'll also mention that NO references are used in the stack, so I don't know how this is happening.
 [2021-12-10 17:20 UTC] dktapps at pmmp dot io
-Status: Open +Status: Closed
 [2021-12-10 17:20 UTC] dktapps at pmmp dot io
Hi all, the mystery has been solved.

The cause of the confusion was this code: https://github.com/pmmp/PocketMine-MP/blob/eb9012401b6510fc24df44d9ff5545c8094f7471/src/entity/ExperienceManager.php#L201

The variable `$progress` is the name of a parameter, but its value is being overridden by some value from PlayerExperienceChangeEvent, which does not check bounds. TL;DR: This was caused by some third party code.

HOWEVER, the fact that debug backtraces contain the current values of parameter variables, rather than the values actually passed, is a very unexpected and confusing behaviour which really ought to be fixed.
 [2021-12-10 18:03 UTC] requinix@php.net
-Status: Closed +Status: Not a bug
 [2021-12-10 18:03 UTC] requinix@php.net
> HOWEVER, the fact that debug backtraces contain the current values of parameter
> variables, rather than the values actually passed, is a very unexpected and
> confusing behaviour which really ought to be fixed.

See:
https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.other.func-parameter-modified
https://bugs.php.net/bug.php?id=79827
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Dec 12 17:01:31 2024 UTC