簡體   English   中英

如何生成一定范圍內的隨機 BigInteger?

[英]How can I generate a random BigInteger within a certain range?

考慮這種效果很好的方法:

public static bool mightBePrime(int N) {
    BigInteger a = rGen.Next (1, N-1);
    return modExp (a, N - 1, N) == 1;
}

現在,為了滿足我正在接受的 class 的要求, mightBePrime必須接受一個BigInteger N,但這意味着我需要一種不同的方式來生成我的隨機BigInteger a

我的第一個想法是做類似BigInteger a = (N-1) * rGen.NextDouble ()的事情,但是BigInteger不能乘以double

如何生成一個介於 1 和 N-1 之間的隨機BigInteger ,其中 N 是一個BigInteger

Paul 在評論中建議我使用隨機字節生成一個數字,如果它太大,則將其丟棄。 這是我想出的(馬塞爾的回答 + 保羅的建議):

public static BigInteger RandomIntegerBelow(BigInteger N) {
    byte[] bytes = N.ToByteArray ();
    BigInteger R;

    do {
        random.NextBytes (bytes);
        bytes [bytes.Length - 1] &= (byte)0x7F; //force sign bit to positive
        R = new BigInteger (bytes);
    } while (R >= N);

    return R;
}

http://amirshenouda.wordpress.com/2012/06/29/implementing-rsa-c/ 也有幫助。

使用隨機類

public BigInteger getRandom(int length){
    Random random = new Random();
    byte[] data = new byte[length];
    random.NextBytes(data);
    return new BigInteger(data);
}

在找到指定范圍內的有效BigInteger之前,幼稚的實現平均會失敗 64 次。

在最壞的情況下,我的實現平均只會重試 0.5 次(讀作:50% 的次數會在第一次嘗試時找到結果)。

此外,與模算術不同,我的實現保持均勻分布

解釋

我們必須在minmax之間生成一個隨機的BigInteger

  1. 如果min > max ,我們將minmax交換
  2. 為了簡化實現,我們將范圍從[min, max]移到[0, max-min] ,這樣我們就不必處​​理符號位
  3. 我們計算max包含多少字節( bytes.Length
  4. 從最高位開始,我們計算有多少位是 0 ( zeroBits )
  5. 我們生成一個隨機字節序列。 bytes.Length字節
  6. 我們知道,對於我們的序列< max ,從最高有效位起至少zeroBits必須為 0,因此我們使用zeroBitMask來設置它們,並在最高有效字節上進行單個位到位&操作,這將通過減少生成超出我們范圍的數字的變化來節省大量時間
  7. 我們檢查我們生成的數字是否> max ,如果是,我們再試一次
  8. 我們通過將min添加到我們的結果中[0, max-min]將范圍從[0, max-min]移回[min, max]

我們有我們的號碼。 😊

執行

public static BigInteger RandomInRange(RandomNumberGenerator rng, BigInteger min, BigInteger max)
{
    if (min > max)
    {
        var buff = min;
        min = max;
        max = buff;
    }

    // offset to set min = 0
    BigInteger offset = -min;
    min = 0;
    max += offset;

    var value = randomInRangeFromZeroToPositive(rng, max) - offset;
    return value;
}

private static BigInteger randomInRangeFromZeroToPositive(RandomNumberGenerator rng, BigInteger max)
{
    BigInteger value;
    var bytes = max.ToByteArray();

    // count how many bits of the most significant byte are 0
    // NOTE: sign bit is always 0 because `max` must always be positive
    byte zeroBitsMask = 0b00000000;

    var mostSignificantByte = bytes[bytes.Length - 1];

    // we try to set to 0 as many bits as there are in the most significant byte, starting from the left (most significant bits first)
    // NOTE: `i` starts from 7 because the sign bit is always 0
    for (var i = 7; i >= 0; i--)
    {
        // we keep iterating until we find the most significant non-0 bit
        if ((mostSignificantByte & (0b1 << i)) != 0)
        {
            var zeroBits = 7 - i;
            zeroBitsMask = (byte)(0b11111111 >> zeroBits);
            break;
        }
    }

    do
    {
        rng.GetBytes(bytes);

        // set most significant bits to 0 (because `value > max` if any of these bits is 1)
        bytes[bytes.Length - 1] &= zeroBitsMask;

        value = new BigInteger(bytes);

        // `value > max` 50% of the times, in which case the fastest way to keep the distribution uniform is to try again
    } while (value > max);

    return value;
}

測試

using (var rng = RandomNumberGenerator.Create())
{
    BigInteger min = 0;
    BigInteger max = 5;

    var attempts = 10000000;
    var count = new int[(int)max + 1];

    var sw = Stopwatch.StartNew();

    for (var i = 0; i < attempts; i++)
    {
        var v = BigIntegerUtils.RandomInRange(rng, min, max);
        count[(int)v]++;
    }

    var time = sw.Elapsed;
    Console.WriteLine("Generated {0} big integers from {1} to {2} in {3}", attempts, min, max, time);
    Console.WriteLine("On average: {0} ms/integer or {1} integers/second", time.TotalMilliseconds / attempts, attempts / time.TotalSeconds);

    for (var i = 0; i <= max; i++)
        Console.WriteLine("{0} generated {1}% of the times ({2} times)", i, count[i] * 100d / attempts, count[i]);
}

我的 i7-6500U 上的測試輸出:

Generated 10000000 big integers from 0 to 5 in 00:00:09.5413677
On average: 0.00095413677 ms/integer or 1048067.77334449 integers/second
0 generated 16.66633% of the times (1666633 times)
1 generated 16.6717% of the times (1667170 times)
2 generated 16.66373% of the times (1666373 times)
3 generated 16.6666% of the times (1666660 times)
4 generated 16.68271% of the times (1668271 times)
5 generated 16.64893% of the times (1664893 times)

我的 i7-6500U 上的另一個測試輸出

Generated 10000000 big integers from 0 to 10^100 in 00:00:17.5036570
On average: 0.0017503657 ms/integer or 571309.184132207 integers/second

創建字節數組並轉換為BigInteger:

public BigInteger random_generate(BigInteger maxValue)
{
    Random random = new Random();
    byte[] maxValue_array = maxValue.ToByteArray();
    byte[] randomValue_array = new byte[maxValue_array.Count()];
    bool on_limit = true; //make sure randomValue won't greater than maxValue
    for (int generate_byte = maxValue_array.Count() - 1; generate_byte >= 0; generate_byte--)
    {
        byte random_byte = 0;
        if (on_limit)
        {
            random_byte = (byte)random.Next(maxValue_array[generate_byte]);
            if (random_byte != (byte)random.Next(maxValue_array[generate_byte]))
            {
                on_limit = false;
            }
        }
        else
        {
            random_byte = (byte)random.Next(256);
        }
        randomValue_array[generate_byte] = random_byte;
    }
    return new BigInteger(randomValue_array);
}

如果maxValue太小,則random會生成相同的值。 所以你可以在函數外面設置隨機:

static void Main(string[] args)
{
    Random random = new Random();
    BigInteger i = random_generate(10, random); //10 is just a example
}

public BigInteger random_generate(BigInteger maxValue, Random random)
{
    byte[] maxValue_array = maxValue.ToByteArray();
    //...rest of the code...
}

這是在不丟棄值的情況下生成范圍內數字並允許 BigIntegers 用於最小值和最大值的另一種方法。

public BigInteger RandomBigInteger(BigInteger min, BigInteger max)
    {
        Random rnd = new Random();
        string numeratorString, denominatorString;
        double fraction = rnd.NextDouble();
        BigInteger inRange;

        //Maintain all 17 digits of precision, 
        //but remove the leading zero and the decimal point;
        numeratorString = fraction.ToString("G17").Remove(0, 2);  

        //Use the length instead of 17 in case the random
        //fraction ends with one or more zeros
        denominatorString = string.Format("1E{0}", numeratorString.Length); 

        inRange = (max - min) * BigInteger.Parse(numeratorString) /
           BigInteger.Parse(denominatorString, 
           System.Globalization.NumberStyles.AllowExponent) 
           + min;
        return inRange;
    }

一般而言,您可能還想指定精度。 這似乎有效。

    public BigInteger RandomBigIntegerInRange(BigInteger min, BigInteger max, int precision)
    {
        Random rnd = new Random();
        string numeratorString, denominatorString;
        double fraction = rnd.NextDouble();
        BigInteger inRange;

        numeratorString = GenerateNumeratorWithSpecifiedPrecision(precision);
        denominatorString = string.Format("1E{0}", numeratorString.Length); 

        inRange = (max - min) * BigInteger.Parse(numeratorString) / BigInteger.Parse(denominatorString, System.Globalization.NumberStyles.AllowExponent) + min;
        return inRange;
    }

    private string GenerateNumeratorWithSpecifiedPrecision(int precision)
    {
        Random rnd = new Random();
        string answer = string.Empty;

        while(answer.Length < precision)
        {
            answer += rnd.NextDouble().ToString("G17").Remove(0, 2);                
        }
        if (answer.Length > precision) //Most likely
        {
            answer = answer.Substring(0, precision);
        }
        return answer;
    } 

這是Random類的NextBigInteger擴展方法。 它基於優秀的 Fabio Iotti 的實現,經過修改以簡潔明了。

/// <summary>
/// Returns a random BigInteger that is within a specified range.
/// The lower bound is inclusive, and the upper bound is exclusive.
/// </summary>
public static BigInteger NextBigInteger(this Random random,
    BigInteger minValue, BigInteger maxValue)
{
    if (minValue > maxValue) throw new ArgumentException();
    if (minValue == maxValue) return minValue;
    BigInteger zeroBasedUpperBound = maxValue - 1 - minValue; // Inclusive
    Debug.Assert(zeroBasedUpperBound.Sign >= 0);
    byte[] bytes = zeroBasedUpperBound.ToByteArray();
    Debug.Assert(bytes.Length > 0);
    Debug.Assert((bytes[bytes.Length - 1] & 0b10000000) == 0);

    // Search for the most significant non-zero bit
    byte lastByteMask = 0b11111111;
    for (byte mask = 0b10000000; mask > 0; mask >>= 1, lastByteMask >>= 1)
    {
        if ((bytes[bytes.Length - 1] & mask) == mask) break; // We found it
    }

    while (true)
    {
        random.NextBytes(bytes);
        bytes[bytes.Length - 1] &= lastByteMask;
        var result = new BigInteger(bytes);
        Debug.Assert(result.Sign >= 0);
        if (result <= zeroBasedUpperBound) return result + minValue;
    }
}

為了返回理想范圍內的值而丟棄的BigInteger實例的百分比平均為 30%(最佳情況為 0%,最壞情況為 50%)。

隨機數的分布是均勻的。

用法示例:

Random random = new();
BigInteger value = random.NextBigInteger(BigInteger.Zero, new BigInteger(1000));

注意:BigInteger.ToByteArray返回的字節結構有詳細記錄(在備注部分),因此假設BigIntegerbyte[]表示不會在未來版本的.NET 平台。 如果發生這種情況,上述NextBigInteger實現可能會以令人討厭的方式失敗,例如進入無限循環或生成錯誤范圍內的數字。 我添加了一些調試斷言,這些斷言永遠不會因當前的表示而失敗,但是對無效條件的檢查的覆蓋面絕不是徹底的。

Fabio Iotti 的最佳答案和公認的答案(基於相同的解決方案)都包含不必要的分配和生成操作。 這是我的RandomExtensions類的優化解決方案。 主要區別:

  • 結果數組緩沖區在知道它是最終結果之前不會轉換為BigInteger因為它會導致額外的分配並且BigInteger實例之間的值比較也更昂貴
  • 如果生成的值太大,則只重新生成 MSB 而不是整個緩沖區,這可以極大地提升性能。

核心實現:

// used by the public methods with 0..max and min..max ranges
private static BigInteger DoGenerateBigInteger(Random random, BigInteger maxValue)
{
    Debug.Assert(maxValue.Sign > 0, "A positive range is expected");

    // We need to determine the raw length as well as and the value of the
    // most significant byte to generate a new value so we obtain the bytes
    // and also reuse the buffer to prevent unnecessary copies.
    byte[] bytes = maxValue.ToByteArray();
    int byteCount = bytes.Length;

    // The last byte can be zero to prevent interpreting the value
    // as a negative two's complement number
    if (bytes[byteCount - 1] == 0)
        --byteCount;

    // Storing a reference to the most significant byte, determining a valid
    // mask for it and saving a copy only of this byte for range check.
    // (see also the remarks below this code block)
    ref byte msb = ref bytes[byteCount - 1];
    Debug.Assert(msb != 0);
    byte mask = (byte)(BitOperations.IsPow2(value)
        ? value - 1
        : (int)BitOperations.RoundUpToPowerOf2((uint)value) - 1);
    byte maxMsb = msb;

    // Filling up the buffer with random bytes. Not creating any intermediate
    // BigInteger instances to prevent unnecessary buffer copies.
    // Applying the mask for MSB reference, whose value has been overwritten.
    random.NextBytes(bytes);
    msb &= mask;

    // Using only the MSB to check whether the generated sample is too large.
    // If so, then regenerating it. Depending on maxMsb we have 0-50% chance
    // that we need to generate the MSB again.
    while (msb >= maxMsb)
        msb = (byte)(random.Next() & mask);

    // If the last byte was empty in the original buffer (indicating it's not
    // a two's complement negative value), then we need to clear it again.
    if (bytes.Length > byteCount)
        bytes[byteCount] = 0;

    // this is the only moment we copy the allocated buffer
    return new BigInteger(bytes);
}

備注:以上代碼使用BitOperations.RoundUpToPowerOf2方法來確定掩碼。 如果您的 CPU 支持,它可以使用硬件加速,但在實踐中我發現它通常比純軟件解決方案慢。 當您針對BitOperations不可用的舊平台時,它也很有用。

最后,您只需要幾個公共Next...方法。 您可以將它們定義為Random類的擴展:

// [0..maxValue)
public static BigInteger NextBigInteger(this Random random, BigInteger maxValue)
{
    if (random == null)
        throw new ArgumentNullException(nameof(random));
    if (maxValue < BigInteger.Zero)
        throw new ArgumentOutOfRangeException(nameof(maxValue), Res.ArgumentMustBeGreaterThanOrEqualTo(0));

    if (maxValue.IsZero || maxValue.IsOne)
        return BigInteger.Zero;

    return DoGenerateBigInteger(random, maxValue);
}

// [minValue..maxValue)
public static BigInteger NextBigInteger(this Random random, BigInteger minValue, BigInteger maxValue)
{
    if (random == null)
        throw new ArgumentNullException(nameof(random));
    if (maxValue <= minValue)
    {
        if (minValue == maxValue)
            return minValue;
        throw new argumentOutOfRangeException(nameof(maxValue), Res.MaxValueLessThanMinValue);
    }

    if (minValue.IsZero)
        return random.NextBigInteger(maxValue, inclusiveUpperBound);

    // of course, these operations cause some extra allocations but these are necessary
    BigInteger range = maxValue - minValue;
    if (range.IsOne)
        return minValue;

    return DoGenerateBigInteger(random, range) + minValue;
}

對於我的用例,我執行了以下操作:

Random rnd = new Random();
BigInteger myVal = rnd.NextBigInteger(50,100); //returns a 50-99 bit BigInteger

代碼:

/// <summary>
/// Returns a random BigInteger with a minimum bit count between <paramref name="minBitLength"/>(inclusive) and <paramref name="maxBitLength"/>(exclusive).
/// </summary>
/// <param name="minBitLength">The inclusive lower bit length of the random BigInteger returned.</param>
/// <param name="maxBitLength">The exclusive upper bit length of the random BigInteger returned. <paramref name="maxBitLength"/> must be greater than or equal to minValue.</param>
public static BigInteger NextBigInteger(this Random rnd, int minBitLength, int maxBitLength)
{
    if (minBitLength < 0) throw new ArgumentOutOfRangeException();
    int bits = rnd.Next(minBitLength, maxBitLength);
    if (bits == 0) return BigInteger.Zero;
    byte[] bytes = new byte[(bits + 7) / 8];
    rnd.NextBytes(bytes);
    // For the top byte, place a leading 1-bit then downshift to achieve desired length.
    bytes[^1] = (byte)((0x80 | bytes[^1]) >> (7 - (bits - 1) % 8));
    return new BigInteger(bytes, true);
}

示例結果:

                        ____Example Lengths___ ___Example Results___
NextBigInteger(0,0) ==> 0 0 0 0 0 0 0 0 0 0 0 |  0  0  0  0  0  0  0
NextBigInteger(0,1) ==> 0 0 0 0 0 0 0 0 0 0 0 |  0  0  0  0  0  0  0
NextBigInteger(0,2) ==> 1 1 1 0 1 0 0 1 0 0 1 |  1  1  1  1  0  1  0
NextBigInteger(0,3) ==> 2 2 2 1 2 0 0 0 1 0 2 |  0  1  0  2  0  1  2
NextBigInteger(0,4) ==> 3 2 0 3 0 0 0 3 1 3 3 |  0  1  1  0  3  1  0
NextBigInteger(0,5) ==> 1 4 1 2 4 1 2 0 3 1 2 |  1  1 10 10 14 11  8
NextBigInteger(0,6) ==> 3 5 1 1 5 5 3 5 1 4 3 |  0  0  1  3  2  7 27
NextBigInteger(1,1) ==> 1 1 1 1 1 1 1 1 1 1 1 |  1  1  1  1  1  1  1
NextBigInteger(1,2) ==> 1 1 1 1 1 1 1 1 1 1 1 |  1  1  1  1  1  1  1
NextBigInteger(1,3) ==> 2 1 2 1 2 2 2 2 1 1 1 |  1  1  1  1  2  2  3
NextBigInteger(1,4) ==> 1 2 3 3 2 1 1 2 2 2 1 |  7  3  1  1  6  1  5
NextBigInteger(1,5) ==> 4 3 1 2 3 1 4 4 1 1 3 |  1  3  1  6  6 12  7
NextBigInteger(1,6) ==> 5 5 4 1 1 2 3 2 1 1 1 |  1 28  7  5 25 15 13
NextBigInteger(2,2) ==> 2 2 2 2 2 2 2 2 2 2 2 |  2  2  3  2  3  2  3
NextBigInteger(2,3) ==> 2 2 2 2 2 2 2 2 2 2 2 |  2  2  3  2  2  3  3
NextBigInteger(2,4) ==> 3 3 2 3 3 3 3 3 3 2 3 |  3  2  7  6  3  3  3
NextBigInteger(2,5) ==> 2 4 2 2 4 4 2 2 4 3 2 |  6  3 13  2  6  4 11
NextBigInteger(2,6) ==> 5 3 5 3 2 3 2 4 4 5 3 |  2  3 17  2 27 14 18
NextBigInteger(3,3) ==> 3 3 3 3 3 3 3 3 3 3 3 |  4  4  5  7  6  7  4
NextBigInteger(3,4) ==> 3 3 3 3 3 3 3 3 3 3 3 |  6  5  4  7  6  4  6
NextBigInteger(3,5) ==> 3 3 3 3 4 4 4 4 3 4 4 |  6 10 12  6  6 15  7
NextBigInteger(3,6) ==> 4 4 3 3 3 4 3 5 4 3 4 | 28 22  5 11 25  8  6
NextBigInteger(4,4) ==> 4 4 4 4 4 4 4 4 4 4 4 | 12  8  8  9  8 10 13
NextBigInteger(4,5) ==> 4 4 4 4 4 4 4 4 4 4 4 | 15 10 10  8 14  8 13
NextBigInteger(4,6) ==> 5 5 5 5 4 5 5 4 5 5 5 | 15 13 14 31 19 15 21

一些隨機的東西:

  • 許多大型隨機數生成器的一個問題是它們可以生成 output,其規模都與 maxValue 相似。 示例:如果我們有類似 RandomBigIntegerUsingValues(min: 100, max:999999999999999) 的東西,那么我們 99% 的結果將在 9999999999999 和 999999999999999 之間。得到低於 1000000 的概率是 1000000000 分之一。
  • 一些范圍檢查由Random.Next()隱式處理。
  • 盡可能匹配 .net 庫擴展方法,因此使用名稱 NextBigInteger(),因為它匹配 Random 的內置 NextSingle()、NextDouble()、NextInt64() 命名。 並且還使用了 .net 的隨機簽名:minBitLength(包含)、maxBitLength(不包含)。
  • 根據 MIT 許可證發布。

以下Range方法將返回您指定范圍內的IEnumerable<BigInteger> 一個簡單的擴展方法將返回 IEnumerable 中的一個隨機元素。

public static IEnumerable<BigInteger> Range(BigInteger from, BigInteger to)
{
    for(BigInteger i = from; i < to; i++) yield return i;
}

public static class Extensions
{
    public static BigInteger RandomElement(this IEnumerable<BigInteger> enumerable, Random rand)
    {
        int index = rand.Next(0, enumerable.Count());
        return enumerable.ElementAt(index);
    }
}

用法:

Random rnd = new Random();
var big = Range(new BigInteger(10000000000000000), new BigInteger(10000000000000020)).RandomElement(rnd);

// 返回隨機值,在本例中為 10000000000000003

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM