php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81627 floats < PHP_INT_MIN are converted to positive ints
Submitted: 2021-11-16 15:49 UTC Modified: 2021-11-22 15:20 UTC
From: shaohua dot li at inf dot ethz dot ch Assigned: cmb (profile)
Status: Not a bug Package: Scripting Engine problem
PHP Version: 8.1Git-2021-11-16 (Git) OS: Ubuntu 20.04.3 LTS
Private report: No CVE-ID: None
 [2021-11-16 15:49 UTC] shaohua dot li at inf dot ethz dot ch
Description:
------------
Hi there,

I compiled php-src twice with clang13 -O0 and -O2 (default). However, for the following code sample, the two `./sapi/cli/php` would evaluate it differently.
For "clang13 -O0" compiled one, "bug" would be printed. However, "clang13 -O2" wouldn't.

Test script:
---------------
<?php
function test() {
    $n = $a = 0;
    while($a <= 0) {
        $a &= $a-- + $a;
        if (++$n > 59) die("bug\n");
    }
}
test();
?>


Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-11-16 23:24 UTC] requinix@php.net
-Status: Open +Status: Not a bug
 [2021-11-16 23:24 UTC] requinix@php.net
> $a &= $a-- + $a;

That statements has two modifications to $a: the -- and the &=. PHP does not guarantee in what order those will happen.

https://en.wikipedia.org/wiki/Sequence_point
 [2021-11-17 08:53 UTC] shaohua dot li at inf dot ethz dot ch
Hi,

Even if I decouple the two operations into two statements, the issue still exists. Also, for the robustness, correctness, and consistency of php, the outputs should be the same.

Test script:
----------------
<?php
function test() {
    $n = 0;
    $a = 0;
    while($a <= 0) {
        $a &= $a + $a;
        $a--;
        if (++$n > 59) die("bug\n");
    }
}
test();
?>
 [2021-11-17 10:32 UTC] requinix@php.net
-Summary: Incorrect result of php +Summary: Incorrect result of php bitwise and with floats using clang13 -O2 -Status: Not a bug +Status: Open
 [2021-11-17 10:32 UTC] requinix@php.net
I assume you do *not* get the float-to-int deprecation warning when $n=58? PHP doesn't support bitwise AND with floats and will round them to ints, but that comes with branch prediction so -O2 may be running afoul of that.

And that there is the limit of my knowledge on this matter.
 [2021-11-17 11:11 UTC] shaohua dot li at inf dot ethz dot ch
Yes, I noticed the warning. I'm just worried that shouldn't all compilers/optimizations emit consistent results even if it's an error?

I also tried gcc11 with -O0 and -O2, on which php emits the same results as clang13 -O2.
 [2021-11-17 11:32 UTC] cmb@php.net
-Package: *General Issues +Package: Scripting Engine problem
 [2021-11-17 11:32 UTC] cmb@php.net
Casting very small floats to int may change the sign:
<https://3v4l.org/Ku5YH>.  I don't think this is particularly
related to clang, nor to branch prediction, but the different
behavior might rather be related to whether
ZEND_DVAL_TO_LVAL_CAST_OK is defined or not[1].  If it is defined,
we cast to zend_long, and for double values outside the range of
zend_long, the behavior is undefined.

On Windows, where ZEND_DVAL_TO_LVAL_CAST_OK is never defined,
zend_dval_to_lval_slow()[2] yields an erroneous result anyway.

[1] <https://github.com/php/php-src/blob/php-7.4.26/Zend/Zend.m4#L158-L187>
[2] <https://github.com/php/php-src/blob/php-7.4.26/Zend/zend_operators.c#L3268-L3280>
 [2021-11-17 12:24 UTC] cmb@php.net
The 32bit implementation of zend_dval_to_lval_slow() has an
explicit comment[1] which says "we're going to make this number
positive".  I don't understand the reasoning, but apparently that
is a deliberate design decision.  Then again I don't understand
why we don't saturate[2].

[1] <https://github.com/php/php-src/blob/php-7.4.26/Zend/zend_operators.c#L3261-L3262>
[2] <https://github.com/php/php-src/commit/77566edbafb969e166239b3fbc929588c6630ee9>
 [2021-11-17 12:42 UTC] cmb@php.net
-Summary: Incorrect result of php bitwise and with floats using clang13 -O2 +Summary: floats < PHP_INT_MIN are converted to positive ints
 [2021-11-17 12:42 UTC] cmb@php.net
Well, should have (also) read the fine manual[1]:

| If the float is beyond the boundaries of int (usually +/-
| 2.15e+9 = 2^31 on 32-bit platforms and +/- 9.22e+18 = 2^63 on
| 64-bit platforms), the result is undefined, since the float
| doesn't have enough precision to give an exact int result.

According to that, the reported issue is not a bug.  On the other
hand, it would allow us to change the behavior to something more
reasonable.

[1] <https://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting.from-float>
 [2021-11-19 01:08 UTC] antonino dot spampinato86 at gmail dot com
When you see "bug" on the screen it indicates an incorrect decrement as it only uses -1, run this code.
In the past I have read but I have lost the ticket, it could also be linked to something else (maybe optimization).

Never break int 60
function test() {
    $n = 0;
    $a = 0;
    $break = false;;
    while($a <= 0) {
        //if($a !== 0)
        //$a = $a - (-1);
        $a &= $a + ($a);
        $a--;
        if (++$n > 59) {
        $break = true;
        break;
        }
    }
return array($a, $n, $break);
}
var_dump(test());

Expected Result:
Deprecated: Implicit conversion from float -1.8446743800977043E+19 to int loses precision in /in/iBpbI on line 10
array(3) {
  [0]=>
  int(135292502015)
  [1]=>
  int(59)
  [2]=>
  bool(false)
}

Break int 60 and error decrement always -1
function test() {
    $n = 0;
    $a = 0;
    $break = false;;
    while($a <= 0) {
        if($a !== 0)
        $a = $a - (-1);
        $a &= $a + ($a);
        //$a--;
        if (++$n > 59) {
        $break = true;
        break;
        }
    }
return array($a, $n, $break);
}
var_dump(test());
Expected -1 Result:
array(3) {
  [0]=>
  int(0)
  [1]=>
  int(60)
  [2]=>
  bool(true)
}
if don't use manual decrement and the same output for $a-- is to equal Expected -1 Result this bug.
 [2021-11-22 15:20 UTC] cmb@php.net
-Status: Open +Status: Not a bug -Assigned To: +Assigned To: cmb
 [2021-11-22 15:20 UTC] cmb@php.net
As per my comment above[1], I'm closing this as not a bug, since
changing the behavior would require the RFC process[2], even
though the behavior is documented as being undefined.

[1] <https://bugs.php.net/bug.php?id=81627#1637152922>
[2] <https://wiki.php.net/rfc/howto>
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 12:01:31 2024 UTC