skip to Main Content

I am trying to create a randomness script that increases the chances of returning true with every day that passes.

For example on January 1st, the odds should be roughly 1:12 of getting true

On December 31st the odds should be 1:1 of getting true.

My first attempt at the script simply used date(‘F’) – 12 and used abs() to reverse any negative. However this resulted in an obvious jump in chance every month. I would instead like to increase the chance daily (or even by the second if possible).

This is what I’ve come up with, but I’m not sure if my logic is right and if there is a better way:

$day = date("z");

$max = abs($day - 365)/30;

$rand = rand(0,$max);

if ($rand == 0)
{
    return true;
}

4

Answers


  1. You can use unix timestamp of two dates, than you will have precision in seconds.

    // Current date timestamp in seconds
    $day = time();
    // End of Dec 31 timestamp in seconds
    $endOfYear = strtotime(date('Y') . '-12-31 23:59:59');
    
    // Closer to end of year, smaller range to select
    $max = max(0, $endOfYear - $day);
    
    $rand = rand(0, $max);
    
    return $rand == 0;
    
    Login or Signup to reply.
  2. Your current solution assumes that rand() can work with floats, it doesn’t.

    What you can do is compute the number of seconds in a year and use that to determine the chance:

    $secondsPerDay  = 60 * 60 * 24;
    $daysPerYear    = 365;
    $secondsPerYear = $secondsPerDay * $daysPerYear;
    $dayOfYear      = date('z'); // 0 ... 365
    $startChange    = 31 * $secondsPerDay; // whole of january
    $endChange      = 0.5 * $daysPerYear * $secondsPerDay; // half a year
    $changeLimit    = $startChange + ($dayOfYear / $daysPerYear) * ($endChange - $startChange);
    
    if (rand(0, $secondsPerYear) > $changeLimit) {
        return TRUE;
    }
    

    Live demo: https://3v4l.org/IOu5D

    So here the rand() function picks a random second for a whole year, and then checks this against $changeLimit. In the beginning of the year $changeLimit will have 1 month worth of seconds, so the change to be TRUE will be 1/12, and at the end of the year it will have half a years worth of seconds, with a change of 1/2.

    This all allows for precise control of the chance at the beginning and the end of the year.

    You can, of course, make this code a lot shorter by not using all those variables, but I think it is needed here to explain what’s going on.

    You also don’t have to do this to the second, as you wanted, because if you do it by day it will have the same result:

    $daysPerYear = 365; 
    $dayOfYear   = date('z'); // 0 ... 365
    $startChange = 31; // whole of january
    $endChange   = $daysPerYear / 2; // half a year
    $changeLimit = $startChange + ($dayOfYear / $daysPerYear) * ($endChange - $startChange);
    
    if (rand(0, $daysPerYear) > $changeLimit) {
        return TRUE;
    }
    

    Live demo: https://3v4l.org/nlrhk

    Login or Signup to reply.
  3. Here is a solution that computes the probability ($prob) for the current timestamp ($t):

    function draw()
    {
        $d = getdate();
        $t = $d[0];
        $t0 = mktime(0, 0, 0, 1, 1, $d['year'] );
        $t1 = mktime(0, 0, 0, 1, 1, $d['year'] + 1);
        $prob = 1/12 + ($t - $t0)/($t1 - $t0) * 11/12;
        return lcg_value() <= $prob;
    }
    

    $t0 is the timestamp of the beginning of the year.

    $t1 is the timestamp of the end of the year.

    If $t is equal to $t0 (we are at the beginning of the year), the probability is 1/12.

    If $t is equal to $t1 (we are at the end of the year), the probability is 1.

    The probability increases linearly from 1/12 to 1.

    lcg_value() draws a random float between 0 and 1.

    Login or Signup to reply.
  4. Here is a function that calculates the probability using the day of the year. The function accepts an optional dateString as argument.

    function randomBooleanByDate($datestring = null) {
        $currentDate = new DateTime($datestring);
        $year = (int)$currentDate->format('Y');
    
        // Get the day of the year (1 for Jan 1st, 365 or 366 for Dec 31st)
        $dayOfYear = (int)$currentDate->format('z') + 1; // 'z' gives 0-based day of year, so +1
        
        
        // Set the minimum and maximum probabilities (January 1st: ~1/12 or 8.33%, December 31st: 1/2)
        $minProbability = 1 / 12; // About 8.33%
        $maxProbability = 0.5; // 50%
        
        // Calculate the current probability based on the progress of the year
        $currentProbability = $minProbability + ($maxProbability - $minProbability) * ($dayOfYear / 365);
    
        // Scale probability to a 1-1000 range (for precision)
        $scaledProbability = (int)($currentProbability * 1000);
        
        // Return true if a random number between 1 and 1000 is less than or equal to the scaled probability
        return mt_rand(1, 1000) <= $scaledProbability;
    }
    

    and here some testcode:

    $testDates = ['2024-01-01', '2024-06-01', '2024-12-31'];
    foreach($testDates as $date) {
        $trueCount = 0;
        for ($i = 0; $i <=1000; $i++) {
            if (randomBooleanByDate($date)) {
                $trueCount++;
            }
        }
    
        echo $date, ': true in ', $trueCount / 10, '%', PHP_EOL;
    }
    
    // returns something like:
    // 2024-01-01: true in 8%
    // 2024-06-01: true in 24.9%
    // 2024-12-31: true in 49.3%
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search