views:

208

answers:

3

A few days ago, you helped me to find out an algorithm for generating random strength values in an online game (thx especially John Rasch).

function getRandomStrength($quality) {
    $rand = mt_rand()/mt_getrandmax();
    $value = round(pow(M_E, ($rand - 1.033) / -0.45), 1);
    return $value;
}

This function generates values between 1.1 and 9.9. Now I want to adjust this function so that it gives me values of the same probability but in another interval, e.g. 1.5 to 8.0. It would be perfect if you could achieve this with additional parameters.

It would be great if you could help me. Thanks in advance!

+2  A: 

Scale and displace the distribution in a normalized range:

D(a,b) = (D(0,1)*(b-a))+a

To get D(0,1) first from the original function D(c,d), do the inverse:

D(0,1) = (D(c,d)-c)/(d-c)

In your case, D is the original function (an exponential function), a is 1.5, b is 8.5, c is 1.1 and d is 9.9

fortran
Sorry, I don't understand this completely. Could you please explain it a little bit more? It seems to be more complex than Jacob B's approach, right? Is the whole function term multiplied by other values (D*number)?
The key to understand the example is that D is not really a function, but an statistic distribution (that can be a Uniform, Exponential, whatever...). I mixed the terminology trying to make it simpler, but maybe I was wrong :-s
fortran
+3  A: 

One way would just be to scale the values:

function getRandomStrength($quality,$min,$max) {
    $rand = mt_rand()/mt_getrandmax();
    $value = round(pow(M_E, ($rand - 1.033) / -0.45), 1);
    $value = $value - 1.1
    $value = $value * ((max-min) / 8.8)
    $value = $value + $min
    return $value;
}
Jacob B
Thank you. I've adjusted your code a bit (some oversights). Is this correct now? http://paste.bradleygill.com/index.php?paste_id=9919
+5  A: 

The values 1.033 and -0.45 in the original code are the magic numbers that provide the scale 1.1 - 9.9. You should get the same results if you pass in 1.1 and 9.9 as the parameters $low and $high in the following code.

function getRandomStrength($low, $high) {
    // TODO: validate the input
    $ln_low = log( $low, M_E );
    $ln_high = log( $high, M_E );
    $scale = $ln_high - $ln_low;

    $rand = ( mt_rand() / mt_getrandmax() ) * $scale + $ln_low;
    $value = round( pow( M_E, $rand), 1 );
    return $value;
}

You should be able to pass in any range for $low and $high and get a logarithmic distribution in that range. (I'll leave range validity checking to you, but 0 < $low < $high should be true.)

This works by back calculating the linear scale necessary to generate the logarithmic scale in the provided range. If I want my log scale to be 1.1 - 9.9, for example, I take the natural log of each of those values, giving me 0.0953 - 2.2925. I then generate a random number in this linear range, and raise e to the random power to convert it back to the log range.

Bill the Lizard
Thanks, this is almost what Jacob B did, isn't it? I tested both algorithms with 50,000 runs and Jacob B's algorithm was a bit faster (0.563871860504 vs 0.468094348907).
+1 - Excellent!
John Rasch
@John Rasch: What aspect in this code is better than in Jacob B's code???
@marco - I would definitely use this answer if I were you, it's not only cleaner, it's more readable, more generalized, understandable, and maintainable. That alone is worth infinitely more than shaving off a tenth of a second over 50,000 runs. If you're that concerned about performance, you may want to consider switching platforms :)
John Rasch
Ok, if you say it it must be true! :) After all, you are the "inventor" of my algorithm so you must know. ;) I've changed the best answer mark.
This is definitely a "cleaner" answer. If I were you I'd go with this one over mine.However, be aware that there are many different logarithmic functions that will return values in any given range. Each of these will have the property that there are fewer high values than low values, but exactly how many will differ.My answer gives exactly the same distribution as you had between 1.1 and 9.9, just "stretched out" linearly. If you want exactly that, use mine: otherwise, use this.
Jacob B
Jacob is right. This implementation will always give you a log distribution over the range you provide. His implementation will skew the distribution. It looks like his code favors lower values, but that might be sensitive to the scale I chose when I ran my simulations. You'll have to check the distributions you're getting at different scales to see which implementation you want.
Bill the Lizard
@John Rasch: Thanks for fixing my parens. I was burning DVDs last night and couldn't spare the cpu cycles to test out my code. :)
Bill the Lizard
Thank you very very much, guys! You helped me alot! :)
@marco92w: You're welcome.
Bill the Lizard