php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #63174 Magic negative number generated by rand
Submitted: 2012-09-27 16:38 UTC Modified: 2017-10-24 09:41 UTC
Votes:3
Avg. Score:4.0 ± 0.8
Reproduced:2 of 2 (100.0%)
Same Version:0 (0.0%)
Same OS:0 (0.0%)
From: ub dot x7b8 at gmail dot com Assigned: nikic (profile)
Status: Closed Package: *Math Functions
PHP Version: Irrelevant OS: 32bit
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: ub dot x7b8 at gmail dot com
New email:
PHP Version: OS:

 

 [2012-09-27 16:38 UTC] ub dot x7b8 at gmail dot com
Description:
------------
rand ( $min, getrandmax());
if $min < 0 then function generates number = getrandmax () - abs ( $min ) + 1 with very high probability.

REPRODUCED: PHP 5.2.6-1 Debian 5.0.10 i386 under VmWare
REPRODUCED: PHP 5.2.6-1 Debian 5.0.5 i386
REPRODUCED: PHP 5.2.3, 5.2.8, 5.2.9, 5.4.7 Debian 6.0.4 i386 under VirtualBox
NOT REPRODUCED: PHP 5.2.1 Solaris 5.10 sparc
NOT REPRODUCED: PHP 5.2.9 Solaris 5.10 sparc
NOT REPRODUCED: PHP 4.4.4-8 Debian 4.0 i386 under VmWare

Test script:
---------------
<?php
$rand_min = -getrandmax (); # 2137483647
$rand_max = getrandmax ();
$c = 1000;
for ( $min = $rand_min; $min < 0; $min+=10000000 ) {
	$diff = $rand_max - abs ( $min ) + 1;
	$cnt = 0;
	for ( $i = 0; $i < $c; $i++ ) {
		$super_random_value = rand ( $min, $rand_max );
		if ( $super_random_value == $diff ) $cnt++;
	}
	$per = $cnt * 100 / $c;
	echo "Number '$diff' was generated $cnt times, " . $per . "%, minimum = $min\n";
} 
?>

Expected result:
----------------
Number '1' was generated 0 times, 0%, minimum = -2147483647
Number '10000001' was generated 0 times, 0%, minimum = -2137483647
Number '20000001' was generated 0 times, 0%, minimum = -2127483647
Number '30000001' was generated 0 times, 0%, minimum = -2117483647
Number '40000001' was generated 0 times, 0%, minimum = -2107483647
...skipped....
Number '2100000001' was generated 0 times, 0%, minimum = -47483647
Number '2110000001' was generated 0 times, 0%, minimum = -37483647
Number '2120000001' was generated 0 times, 0%, minimum = -27483647
Number '2130000001' was generated 0 times, 0%, minimum = -17483647
Number '2140000001' was generated 0 times, 0%, minimum = -7483647

Actual result:
--------------
Number '1' was generated 514 times, 51.4%, minimum = -2147483647
Number '10000001' was generated 507 times, 50.7%, minimum = -2137483647
Number '20000001' was generated 522 times, 52.2%, minimum = -2127483647
Number '30000001' was generated 520 times, 52%, minimum = -2117483647
Number '40000001' was generated 502 times, 50.2%, minimum = -2107483647
...skipped...
Number '2100000001' was generated 12 times, 1.2%, minimum = -47483647
Number '2110000001' was generated 14 times, 1.4%, minimum = -37483647
Number '2120000001' was generated 10 times, 1%, minimum = -27483647
Number '2130000001' was generated 11 times, 1.1%, minimum = -17483647
Number '2140000001' was generated 2 times, 0.2%, minimum = -7483647

Patches

rand-range-double (last revision 2015-07-15 22:32 UTC by cmb@php.net)

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-07-08 21:50 UTC] yohgaki@php.net
It seems working well on 3v4l.
http://3v4l.org/BqqSr
 [2015-07-09 15:52 UTC] bishop@php.net
@yohgaki That's because 3v4l runs 64-bit: http://3v4l.org/a8KaY

This problem only manifests when there's overflow on 32-bit platforms.
 [2015-07-09 15:58 UTC] bishop@php.net
-Assigned To: +Assigned To: bishop
 [2015-07-09 16:12 UTC] bishop@php.net
Duplicated by #70003.
 [2015-07-09 20:56 UTC] yohgaki@php.net
-Operating System: +Operating System: 32bit
 [2015-07-13 19:52 UTC] bishop@php.net
-Status: Assigned +Status: Verified
 [2015-07-15 22:32 UTC] cmb@php.net
The following patch has been added/updated:

Patch Name: rand-range-double
Revision:   1436999569
URL:        https://bugs.php.net/patch-display.php?bug=63174&patch=rand-range-double&revision=1436999569
 [2015-07-15 22:49 UTC] cmb@php.net
> This problem only manifests when there's overflow on 32-bit
> platforms.

ACK. However, on 64bit platforms there's a closely related problem
if $max-$min > PHP_INT_MAX, in which case rand() unevenly
distributes, e.g. running

    <?php
    for ($i = 0; $i < 1000000; $i++) {
            if (rand(PHP_INT_MIN, PHP_INT_MAX) > 0) {
                    echo "Never\n";
                    break;
            }
    }
    ?>
    
never prints "Never". The problem is in RAND_RANGE() where only
the offset is calculated as double, instead of the whole
expression (see the attached patch "rand-range-double"). This
change would also fix the behavior on 32bit architectures.

However, changing the results of (mt_)rand() for $max-$min >
PHP_INT_MAX and with regard to inexact double to integer
conversion on 64bit architectures would be a BC break, so I'm not
sure whether we should apply that for PHP 5. IMHO, changing PHP 7
in this regard would be acceptable even though it's in feature
freeze.

Anyway, I would suggest to better document the behavior for non
default ranges.
 [2017-10-24 05:46 UTC] kalle@php.net
-Status: Verified +Status: Assigned
 [2017-10-24 06:03 UTC] kalle@php.net
-Status: Assigned +Status: Open -Assigned To: bishop +Assigned To:
 [2017-10-24 09:41 UTC] nikic@php.net
-Status: Open +Status: Closed -Assigned To: +Assigned To: nikic
 [2017-10-24 09:41 UTC] nikic@php.net
Biases in mt_rand() have been fixed in PHP 7.1, with a remaining issue fixed in PHP 7.2. As such, this is now resolved.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Dec 22 03:01:28 2024 UTC