質問

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

役に立ちましたか?

解決

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

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

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

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

他のヒント

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

一様にランダムには、一様にランダムに離散と< a href = "http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)" rel = "nofollow noreferrer">連続的に一様にランダム。

有限な数の異なる結果が得られる乱数ジェネレーターでは、一律にランダムにランダムに意味があります。たとえば、1〜10の整数を生成すると、4を取得する確率は7を取得する確率と同じであると予想されます。

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

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

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

一方、浮動小数点数は実数の近似値であると想定されているため、連続乱数ジェネレーターの動作を近似しようとする方がよいでしょう。実際の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);
}

小数の範囲全体で均一な分布を得る方法についても議論できます。おそらくもっと簡単な方法がありますが、このジョンライデグレンの回答は比較的均一な分布を生成するはずです:

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

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

つまり、0から90%のスケールを取得する必要があることを意味します-その範囲には90%の可能性のある範囲が含まれるためです-1から9%のスケールなどです。

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

これは、範囲実装を使用した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);
}

これは古い質問ですが、 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);
}

また、簡単なことの力により、次のことができます。

var rand = new Random();
var item = new decimal(rand.NextDouble());

これには少し戸惑いました。これは私が思いつく最高の方法です:

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の完全な範囲を使用することはできません。

here you ...... 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);
    }

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

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

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

ああ、必要に応じてRNGインスタンスをスレッドセーフとして作成できます。

非常に便利です!

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

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

正直に言うと、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

and

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.9999999999999999999999999999mの数値は、小数点以下が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であると信じています(「信じる」という言葉の強調は、数学を信用していません)。これにより、関数が戻り値を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;
    }
}

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

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

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

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

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);
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top