tags:

views:

3752

answers:

6

How can I get a random System.Decimal? System.Random doesn't support it directly.

+1  A: 

I puzzled with this for a bit. This is the best I could come up with:

public class DecimalRandom : Random
    {
        public override decimal NextDecimal()
        {
            //The low 32 bits of a 96-bit integer. 
            int lo = this.Next(int.MinValue, int.MaxValue);
            //The middle 32 bits of a 96-bit integer. 
            int mid = this.Next(int.MinValue, int.MaxValue);
            //The high 32 bits of a 96-bit integer. 
            int hi = this.Next(int.MinValue, int.MaxValue);
            //The sign of the number; 1 is negative, 0 is positive. 
            bool isNegative = (this.Next(2) == 0);
            //A power of 10 ranging from 0 to 28. 
            byte scale = Convert.ToByte(this.Next(29));

            Decimal randomDecimal = new Decimal(lo, mid, hi, isNegative, scale);

            return randomDecimal;
        }
    }

Edit: As noted in the comments lo, mid and hi can never contain int.MaxValue so the complete range of Decimals isn't possible.

Daniel Ballinger
That'll do it...
Marc Gravell
Not quite... Random.Next(int.MinValue, int.MaxValue) will never return int.MaxValue. I've got an answer, but I think I can improve on it.
Jon Skeet
Statistics is not my strong point, so I'm probably wrong, but I'd worry that the distribution might not be very uniform.
Michael Burr
+7  A: 

EDIT: Removed old version

This is similar to Daniel's version, but will give the complete range. It also introduces a new extension method to get a random "any integer" value, which I think is handy.

Note that the distribution of decimals here is not uniform.

/// <summary>
/// Returns an Int32 with a random value across the entire range of
/// possible values.
/// </summary>
public static int NextInt32(this Random rng)
{
     unchecked
     {
         int firstBits = rng.Next(0, 1 << 4) << 28;
         int lastBits = rng.Next(0, 1 << 28);
         return firstBits | lastBits;
     }
}

public static decimal NextDecimal(this Random rng)
{
     byte scale = (byte) rng.Next(29);
     bool sign = rng.Next(2) == 1;
     return new decimal(rng.NextInt32(), 
                        rng.NextInt32(),
                        rng.NextInt32(),
                        sign,
                        scale);
}
Jon Skeet
I wondered about this and/or the ctor that takes a byte[] - but are all the byte[] permutations legal?
Marc Gravell
@Marc: My recollection is that they are, and the other bits are just ignored. I haven't checked though.
Jon Skeet
I did, it's just passed through a private ctor as if it was legal. You should try that and see if it makes sense. The implementation is hidden (InternalCall) and that junk data could corrupt calculations.
John Leidegren
@John: Right. Will check it out some time. I prefer my revised version anyway. I do like making it an extension method though...
Jon Skeet
@Jon - Yes, it's a lot nicer and with the extension, that's how I would do it to. But you could just as easily implement your NextInt32 in a similar fashion to mine. It's to bad that the Random class is this limited thing, it could do with some by ref and unsafe methods.
John Leidegren
I remember you said in one of your posts "... not all exponent combinations are valid. Only values 0-28 work..." (http://www.yoda.arachsys.com/csharp/decimal.html). Doesn't this mean that the old version here could produce invalid values?
Hosam Aly
@Hosam Aly: Well, the ReadDecimal method constructs the decimal by accessing a private constructor, this ctor does not test whether the exponent is valid or not. If the InternalCall methods don't check this then any calculation involving that number would be incorrect.
John Leidegren
Yes, I think the old version is too dodgy in general. Removing...
Jon Skeet
I have been thinking about this, and I believe it will not produce a uniform distribution, but will rather favour larger numbers. I think we should give the second int a 33% probability of having a 0 value, and the third int a 67% probablity of 0.
Hosam Aly
And even the scale needs to be adjusted to favour lower values, as (I think) this would correspond better to the actual uniform distribution of values.
Hosam Aly
@Hosam: It's absolutely non-uniform in terms of magnitude of decimal. It's uniform in terms of each bit pattern having the same probability of occurring. For a uniform pattern, we'd be better off generating a number between 0 and 1.
Jon Skeet
I marked this answer as accepted as it overcomes the issue I had with getting the full range of Int32 values (particularly int.MaxValue). I hadn't really thought about the distribution of random values I was expecting. If repetitions are an issue the Faber and Aly solution may be more appropriate.
Daniel Ballinger
@Daniel, for the record, I don't deserve any credit for that answer. I just fixed a typo.
Hosam Aly
@Jon: I see that you left a comment regarding the fact that this does not produce a uniform distribution, but I do think that should be lifted into the body of your answer. I think too many people rip code without understanding its behavior and that we have a responsibility to provide such warnings (lest we end up flying on a plane guided by poorly understood copied code).
Jason
@Jason: Will do.
Jon Skeet
+4  A: 

I like Jon Skeet's second approach, here's a third alternative. Never mind my subclassing trick, this should be written as an extension method. The protected Sample() method is exposed by the public NextDouble() method (which internally, when you specify a custom range, is being used to generate the numbers).

public static decimal NextDecimal(this Random r)
{
    var a = (int)(uint.MaxValue * r.NextDouble());
    var b = (int)(uint.MaxValue * r.NextDouble());
    var c = (int)(uint.MaxValue * r.NextDouble());
    var n = r.NextDouble() > 0.5;
    var s = (byte)(29 * r.NextDouble());
    return new Decimal(a, b, c, n, s);
}
John Leidegren
I don't like the bit which works out the scale in the last line, because it won't give a random distribution of scales. It's taking (0-127) % 29, which favours 0-11 over 12-28 if my maths is right.
Jon Skeet
(Of course the desired distribution hasn't been specified, but this would seem a somewhat odd distribution.)
Jon Skeet
@Jon: Very true, I didn't really care about that. I believe that pseudo-randomness is just that. Fake, but you would probably expect less biased distribution.
John Leidegren
@Jon: I fixed that and instead subclass the Random class because it has a Sample() method. Still, I blame the Random class for poor extendability.
John Leidegren
+2  A: 

here you go... uses the crypt library to generate a couple of random bytes, then convertes them to a decimal value... see MSDN for the decimal constructor

using System.Security.Cryptography;

public static decimal Next(decimal max)
{
    // Create a int array to hold the random values.
    Byte[] randomNumber = new Byte[] { 0,0 };

    RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();

    // Fill the array with a random value.
    Gen.GetBytes(randomNumber);

    // convert the bytes to a decimal
    return new decimal(new int[] 
    { 
               0,                   // not used, must be 0
               randomNumber[0] % 29,// must be between 0 and 28
               0,                   // not used, must be 0
               randomNumber[1] % 2  // sign --> 0 == positive, 1 == negative
    } ) % (max+1);
}

revised to use a different decimal constructor to give a better range of numbers

public static decimal Next(decimal max)
{
    // Create a int array to hold the random values.
    Byte[] bytes= new Byte[] { 0,0,0,0 };

    RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();

    // Fill the array with a random value.
    Gen.GetBytes(bytes);
    bytes[3] %= 29; // this must be between 0 and 28 (inclusive)
    decimal d = new decimal( (int)bytes[0], (int)bytes[1], (int)bytes[2], false, bytes[3]);

        return d % (max+1);
    }
Muad'Dib
That does mean we're limited to 65536 values out of the huge possible range though, doesn't it?
Jon Skeet
yeah, it does. :-(
Muad'Dib
Are we living in the days of 16-bit computing? What's the meaning of this?
John Leidegren
+3  A: 
Rasmus Faber
Why is your scale ratio 0.1? It doesn't seem uniform to me. Perhaps 1.0/28 is more uniform.
Hosam Aly
And how is this different from `28 * r.NextDouble()`?
Hosam Aly
That's quite interesting, because we're totally just putting in garbage. Supposedly you would wanna have a nice uniform distribution between 0 and 1 and then you would scale that with what ever range you require. Though I fail to see how this accomplish that?
John Leidegren
@Hosam Aly I have tried to explain in more depth what I am doing. Basically, you want to get scale=28 90% of the time, since that contains the largest range of numbers. 28*r.NextDouble() makes it as likely to get a number between 0.1 and 0.2 as it is to get a number between 100000000 and 200000000.
Rasmus Faber
@John Leidegren: Yes, it usually would be easier to start off with a uniform distribution between 0 and 1 and just scale that. I think I will make an answer that provides that.
Rasmus Faber
Sorry, it just occurred to me, that I had inverted the meaning of scale. So replace scale=x with scale=28-x in my comments.
Rasmus Faber
A: 

static decimal GetRandomDecimal() {

        int[] DataInts = new int[4];
        byte[] DataBytes = new byte[DataInts.Length * 4];

        // Use cryptographic random number generator to get 16 bytes random data
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

        do
        {

            rng.GetBytes(DataBytes);

            // Convert 16 bytes into 4 ints
            for (int index = 0; index < DataInts.Length; index++)
            {
                DataInts[index] = BitConverter.ToInt32(DataBytes, index * 4);
            }

            // Mask out all bits except sign bit 31 and scale bits 16 to 20 (value 0-31)
            DataInts[3] = DataInts[3] & (unchecked((int)2147483648u | 2031616));

          // Start over if scale > 28 to avoid bias 
        } while (((DataInts[3] & 1835008) == 1835008) && ((DataInts[3] & 196608) != 0));

        return new decimal(DataInts);

    }