2009-03-04 102 views
52

如何獲得一個隨機的System.Decimal? System.Random不直接支持它。在C中生成一個隨機十進制數

+4

豈不是更容易產生之間的隨機整數例如1和999,並將結果除以100?恩。隨機數1爲0.01,999爲9.99 – 2012-04-19 10:47:48

+0

@StefanZCamilleri取決於你需要的隨機小數。閱讀下面的一些答案。可以表示的可能的十進制值的完整範圍非常大,並且需要一些想法來獲得隨機整數以供入十進制構造函數。那麼存在產生的十進制值的隨機分佈如何均勻的問題。 – 2012-04-20 01:05:28

+0

此外,如果我沒有弄錯,.01不在1到999之間。因此小於1不會是有效的迴應。 – 2017-11-17 15:31:24

回答

40

編輯:刪除舊版本

這是類似丹尼爾的版本,但會給完整範圍。它還引入了一種新的擴展方法來獲得一個隨機的「任何整數」值,我認爲這很方便。

請注意,這裏的小數點分佈不統一

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

我對此感到困惑不解。這是我能想出的最好:

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

編輯:正如在評論中羅,中注意到並喜不能包含int.MaxValue這樣的小數齊全是不可能的。

+0

這樣做... – 2009-03-04 07:09:10

+0

不完全... Random.Next(int.MinValue,int.MaxValue)永遠不會返回int.MaxValue。我有一個答案,但我想我可以改進它。 – 2009-03-04 07:13:45

1

在這裏你去...使用地下室庫生成一對夫婦隨機字節,然後將它們convertes爲十進制值...見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); 
} 

修改爲使用不同小數構造以提供更好的數字範圍

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

您通常會從一個隨機數發生器指望它不僅產生隨機數,而且數字是隨機產生的。

均勻隨機有兩種定義:discrete uniformly randomcontinuous uniformly random

離散均勻隨機對於具有有限數量的不同可能結果的隨機數生成器是有意義的。例如產生1到10之間的整數你會然後期望獲得4的概率是與獲取7.

連續均勻地隨機有意義當隨機數發生器的範圍內生成的數字。例如,生成0和1之間的實數的發電機你會然後期望得到0和0.5之間的數的概率是相同的獲得0.5和1.

當隨機數生成器之間的數字生成浮點數(這基本上是一個System.Decimal是什麼 - 它只是基於10的浮點),這是可爭議的一致隨機的正確定義是什麼:

一方面,由於浮動在計算機中使用固定數量的比特來表示點數,顯然可能的結果數量是有限的。所以人們可以爭辯說,正確的分佈是一個離散的連續分佈,每個可表示的數字具有相同的概率。這基本上就是Jon Skeet'sJohn Leidegren's的實現。另一方面,有人可能會爭辯說,因爲浮點數應該是對實數的近似值,所以通過嘗試近似連續隨機數發生器的行爲會更好 - 儘管如此,實際的RNG實際上是離散的。這是你從Random獲得的行爲。NextDouble(),其中 - 儘管0.00001-0.00002範圍內的可表示數字大約與0.8-0.9範圍內的可表示數字大致相同,但您在第二個範圍內獲得數字的可能性要大1000倍 - 就像您期望。

因此,Random.NextDecimal()的正確實現應該可以連續均勻分佈。

這裏是喬恩斯基特的回答簡單的變化是均勻的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); 
} 

基本上,我們確保規模的值按比例選擇相應範圍的大小。

這意味着,我們應該得到的時間0 90%的比例 - 因爲該範圍包含可能的範圍內的90% - 的時間1 9%的比例,等等

還有實現中的一些問題,因爲它確實考慮到一些數字有多個表示 - 但它應該比其他實現更接近均勻分佈。

1
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 
5

這裏是十進制隨機與範圍實施,對我來說很好。

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

請查看以下鏈接現成的實現,應該有所幫助:

MathNet.Numerics, Random Numbers and Probability Distributions

廣泛分佈是特別感興趣的,建立在隨機數發生器的頂部(梅森旋轉算法等)直接從System.Random派生,所有這些都提供了方便的擴展方法(例如NextFullRangeInt32,NextFullRangeInt64,NextDecimal等)。當然,您可以使用默認的SystemRandomSource,它只是System.Random的擴展方法。

哦,你可以創建你的RNG實例作爲線程安全,如果你需要它。

的確很方便!

這是一個老問題,但對於那些剛剛閱讀它的人來說,爲什麼要重新發明輪子?

3

我知道這是一個老問題,但distribution issue Rasmus Faber described一直在困擾着我,所以我想出了以下內容。我還沒有詳細查看NextInt32 implementation provided by Jon Skeet,我假設(希望)它與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); 
} 
1

說實話,我不認爲C#十進制的內部格式是很多人認爲的方式。出於這個原因,這裏提出的至少一些解決方案可能無效或可能不一致。考慮下面的2號以及它們是如何存儲在十進制格式:

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位整數部分,但會使比例保持不變。

在實驗中,我發現數字0.9999999999999999999999999999,其中有28個9,在小數點之前可能有9的最大數量將達到1.0m。

而且實驗證明了如下代碼設置變量「DEC」的值0.9999999999999999999999999999米:

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

正是從這個發現,我想出了擴展,隨機類中可以看到下面的代碼。我相信這些代碼功能齊全,運行良好,但會爲其他人的眼睛檢查錯誤而感到高興。我不是一個統計學家,所以我不能說這個代碼是否產生了一個真正統一的小數分佈,但是如果我不得不猜測,我會說它完美失敗,但非常接近(如51個億中的1個呼叫贊成一定範圍的數字)。

第一個NextDecimal()函數應產生等於或大於0.0m且小於1.0m的值。 do/while語句通過循環防止RandH和RandL超過值0.99999999999999d,直到它們低於該值。我相信這個循環重複的機率是51萬億分之一(強調單詞相信,我不相信我的數學)。這反過來應該防止函數將返回值四捨五入至1.0m。

第二個NextDecimal()函數應該和Random.Next()函數一樣工作,只用十進制值而不用整數。我實際上並沒有使用第二個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; 
    } 
} 
2

同時,這也是通過簡單的東西力量,要做到:

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

我想產生「隨機」小數多達9位小數。我的做法是產生一個雙精度數,並將其除以小數。

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」 S在劃分

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