web-dev-qa-db-ja.com

C#でのランダムな10進数の生成

ランダムSystem.Decimalを取得するにはどうすればよいですか? System.Randomは直接サポートしていません。

59

編集:古いバージョンを削除

これはダニエルのバージョンに似ていますが、完全な範囲を提供します。また、ランダムな「任意の整数」値を取得するための新しい拡張メソッドも導入していますが、これは便利だと思います。

ここでの小数の分布は均一ではないであることに注意してください。

/// <summary>
/// Returns an Int32 with a random value across the entire range of
/// possible values.
/// </summary>
public static int NextInt32(this Random rng)
{
     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);
}
44
Jon Skeet

通常、乱数ジェネレーターからは、乱数が生成されるだけでなく、その数字が一様にランダムに生成されることが期待されます。

一様ランダムには、2つの定義があります: 離散一様ランダム および 連続一様ランダム

離散的に一様なランダムは、有限の数の異なる可能な結果を​​持つ乱数ジェネレーターにとって意味があります。たとえば、1〜10の整数を生成すると、4を取得する確率は7を取得する確率と同じになると予想されます。

乱数ジェネレーターが範囲内の数値を生成する場合、連続的に一様にランダムに意味があります。たとえば、0〜1の実数を生成するジェネレーターでは、0〜0.5の数を取得する確率は、0.5〜1の数を取得する確率と同じであると予想されます。

乱数ジェネレーターが浮動小数点数を生成するとき(これは基本的にSystem.Decimalと同じです-基数10の浮動小数点です)、一様乱数の適切な定義は次のとおりです。

一方では、浮動小数点数はコンピューター内の固定ビット数で表されているため、可能な結果の数が有限であることは明らかです。したがって、適切な分布は、それぞれの表現可能な数が同じ確率を持つ離散的連続分布であると主張できます。それが基本的に Jon Skeet's および John Leidegren's の実装です。

一方、浮動小数点数は実数の近似値であると想定されるため、連続乱数ジェネレーターの動作を近似することで、実際のRNGは実際に離散。これは、Random.NextDouble()から取得する動作です。0.00001〜0.00002の範囲には、0.8〜0.9の範囲とほぼ同じ数の表現可能な数値がありますが、 2番目の範囲の数値-予想どおり。

したがって、Random.NextDecimal()の適切な実装は、おそらく継続的に均一に分散されるはずです。

以下は、Jon Skeetの答えの簡単なバリエーションであり、0から1の間に均一に分布しています(NextInt32()拡張メソッドを再利用します)。

public static decimal NextDecimal(this Random rng)
{
     return new decimal(rng.NextInt32(), 
                        rng.NextInt32(),
                        rng.Next(0x204FCE5E),
                        false,
                        0);
}

また、小数の範囲全体で均一な分布を取得する方法について議論することもできます。おそらくもっと簡単な方法がありますが、 John Leidegren's answer のこのわずかな変更により、比較的均一な分布が生成されます。

private static int GetDecimalScale(Random r)
{
  for(int i=0;i<=28;i++){
    if(r.NextDouble() >= 0.1)
      return i;
  }
  return 0;
}

public static decimal NextDecimal(this Random r)
{
    var s = GetDecimalScale(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;
    return new Decimal(a, b, c, n, s);
}

基本的に、スケールの値は、対応する範囲のサイズに比例して選択されるようにします。

つまり、時間の90%のスケールを取得する必要があります-その範囲には可能な範囲の90%が含まれるため-時間の1%のスケールなど.

いくつかの数値に複数の表現があることを考慮に入れているため、実装にはまだいくつかの問題がありますが、他の実装よりも均一な分布にはるかに近いはずです。

10
Rasmus Faber

これは、私にとってはうまく機能する範囲実装による10進ランダムです。

public static decimal NextDecimal(this Random rnd, decimal from, decimal to)
{
    byte fromScale = new System.Data.SqlTypes.SqlDecimal(from).Scale;
    byte toScale = new System.Data.SqlTypes.SqlDecimal(to).Scale;

    byte scale = (byte)(fromScale + toScale);
    if (scale > 28)
        scale = 28;

    decimal r = new decimal(rnd.Next(), rnd.Next(), rnd.Next(), false, scale);
    if (Math.Sign(from) == Math.Sign(to) || from == 0 || to == 0)
        return decimal.Remainder(r, to - from) + from;

    bool getFromNegativeRange = (double)from + rnd.NextDouble() * ((double)to - (double)from) < 0;
    return getFromNegativeRange ? decimal.Remainder(r, -from) + from : decimal.Remainder(r, to);
}
6
Danil

これは古い質問ですが、 Rasmus Faberが説明した配布の問題 が私を悩ませ続けているので、次のことを思いつきました。 Jon Skeetが提供するNextInt32の実装 を詳しく調べていませんが、 Random.Next() と同じ分布を持っていると(期待して)います。

//Provides a random decimal value in the range [0.0000000000000000000000000000, 0.9999999999999999999999999999) with (theoretical) uniform and discrete distribution.
public static decimal NextDecimalSample(this Random random)
{
    var sample = 1m;
    //After ~200 million tries this never took more than one attempt but it is possible to generate combinations of a, b, and c with the approach below resulting in a sample >= 1.
    while (sample >= 1)
    {
        var a = random.NextInt32();
        var b = random.NextInt32();
        //The high bits of 0.9999999999999999999999999999m are 542101086.
        var c = random.Next(542101087);
        sample = new Decimal(a, b, c, false, 28);
    }
    return sample;
}

public static decimal NextDecimal(this Random random)
{
    return NextDecimal(random, decimal.MaxValue);
}

public static decimal NextDecimal(this Random random, decimal maxValue)
{
    return NextDecimal(random, decimal.Zero, maxValue);
}

public static decimal NextDecimal(this Random random, decimal minValue, decimal maxValue)
{
    var nextDecimalSample = NextDecimalSample(random);
    return maxValue * nextDecimalSample + minValue * (1 - nextDecimalSample);
}
5
Bryan Loeper

また、簡単な機能を使用して、次のことを行います。

var Rand = new Random();
var item = new decimal(Rand.NextDouble());
3
Adam Wells

私はこれで少し困惑しました。これは私が思いつくことができる最高のものです:

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;
        }
    }

編集:コメントlo、mid、hiに記載されているように、int.MaxValueを含めることはできないため、Decimalの完全な範囲は使用できません。

2

正直に言うと、C#の10進数の内部形式が多くの人が考える方法で機能するとは思わない。このため、ここに提示されているソリューションの少なくとも一部は無効であるか、一貫して機能しない可能性があります。次の2つの数値と、それらが10進形式でどのように格納されるかを考慮してください。

0.999999999999999m
Sign: 00
96-bit integer: 00 00 00 00 FF 7F C6 A4 7E 8D 03 00
Scale: 0F

そして

0.9999999999999999999999999999m
Sign: 00
96-bit integer: 5E CE 4F 20 FF FF FF 0F 61 02 25 3E
Scale: 1C

スケールがどのように異なるかについて特に注意してください。ただし、両方の値はほぼ同じです。つまり、どちらも1未満であり、ごくわずかです。直接の関係があるのは、目盛りと桁数のようです。何かが足りない限り、これは小数の96ビット整数部分を改ざんするが、スケールを変更しないままにするほとんどのコードにモンキーレンチを投げるはずです。

実験では、28の9がある0.99999999999999999999999999999999mの数値は、小数点以下が1.0mに切り上げられるまでに可能な最大9の数値を持っていることがわかりました。

さらなる実験により、次のコードが変数「Dec」を値0.99999999999999999999999999999999mに設定することが証明されました。

double DblH = 0.99999999999999d;
double DblL = 0.99999999999999d;
decimal Dec = (decimal)DblH + (decimal)DblL / 1E14m;

この発見から、以下のコードで見ることができるRandomクラスの拡張を思いついたのです。このコードは完全に機能し、正常に機能していると思いますが、他の人がミスをチェックしてくれると嬉しいです。私は統計学者ではないので、このコードが小数の真に均一な分布を生成するかどうかは言えませんが、もし私が推測しなければならないなら、それは完璧に失敗すると言いますが、非常に近くなります(51兆回の呼び出しのようにある範囲の数字)。

最初のNextDecimal()関数は、0.0m以上1.0m未満の値を生成する必要があります。 do/whileステートメントは、RandHとRandLが値0.99999999999999dを超えるのを、それらがその値を下回るまでループすることによって防ぎます。このループが繰り返される確率は51兆回に1回であると信じています(Wordを重視しているので、数学を信用していません)。これにより、関数が戻り値を1.0mに丸めるのを防ぐことができます。

2番目のNextDecimal()関数は、Random.Next()関数と同じように機能する必要があります。整数ではなくDecimal値のみが使用されます。私は実際にこの2番目のNextDecimal()関数を使用しておらず、テストもしていません。かなり単純なので、私は正しいと思いますが、繰り返しますが、テストしていません-頼る前に正しく動作することを確認したいと思うでしょう。

public static class ExtensionMethods {
    public static decimal NextDecimal(this Random rng) {
        double RandH, RandL;
        do {
            RandH = rng.NextDouble();
            RandL = rng.NextDouble();
        } while((RandH > 0.99999999999999d) || (RandL > 0.99999999999999d));
        return (decimal)RandH + (decimal)RandL / 1E14m;
    }
    public static decimal NextDecimal(this Random rng, decimal minValue, decimal maxValue) {
        return rng.NextDecimal() * (maxValue - minValue) + minValue;
    }
}
1
Darin
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);
    }
    //end
1
Melvil Dewey

役立つはずの既製の実装については、次のリンクを確認してください。

MathNet.Numerics、乱数および確率分布

特に興味深いのは、System.Randomから直接派生した乱数ジェネレーター(MersenneTwisterなど)の上に構築され、すべて便利な拡張メソッド(NextFullRangeInt32、NextFullRangeInt64、NextDecimalなど)を提供することです。もちろん、デフォルトのSystemRandomSourceを使用できます。これは、単に拡張メソッドで装飾されたSystem.Randomです。

ああ、必要な場合は、RNGインスタンスをスレッドセーフとして作成できます。

本当にとても便利です!

これは古い質問ですが、読んでいるだけの人にとって、なぜ車輪を再発明するのでしょうか?

1
Ben Stabile

ここに行く... cryptライブラリを使用していくつかのランダムバイトを生成し、それらを10進数値に変換します... 10進数コンストラクターのMSDN を参照してください

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);
}

より良い範囲の数値を提供するために異なる10進コンストラクターを使用するように修正されました

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);
    }
1
Muad'Dib

小数点以下9桁までの「ランダムな」小数を生成したかった。私のアプローチは、単にdoubleを生成し、それを小数用に分割することでした。

int randomInt = rnd.Next(0, 100);

double randomDouble = rnd.Next(0, 999999999);
decimal randomDec = Convert.ToDecimal(randomint) + Convert.ToDecimal((randomDouble/1000000000));

「randomInt」は小数点の前の数字です。0を入力するだけです。小数点を減らすには、単に「9」をランダムに、「0」を除算で削除します。

0
humudu

OPの質問は非常に受け入れやすく、制限なしでランダムなSystem.Decimalが必要なため、以下は非常にシンプルな解決策です。

生成された数値の均一性や精度には関心がなかったので、制限がある場合は他の回答の方がおそらく良いかもしれませんが、これは単純なケースではうまく機能します。

Random rnd = new Random();
decimal val;
int decimal_places = 2;
val = Math.Round(new decimal(rnd.NextDouble()), decimal_places);

私の特定のケースでは、お金の文字列として使用するランダムな小数を探していたので、私の完全な解決策は次のとおりでした。

string value;
value = val = Math.Round(new decimal(rnd.NextDouble()) * 1000,2).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
0
James