c# - Número aleatorio entre int.MinValue y int.MaxValue, inclusive




.net random (10)

¿Qué hay de esto?

   using System;

    public class Example
    {
       public static void Main()
       {
          Random rnd = new Random();
          int min_value = max_value;
          int max_value = min_value;

          Console.WriteLine("\n20 random integers from 10 to 20:");
          for (int ctr = 1; ctr <= 20; ctr++) 
          {
             Console.Write("{0,6}", rnd.Next(min_value, max_value));
             if (ctr % 5 == 0) Console.WriteLine();
          }
       }
    }

Aquí hay un poco de rompecabezas: Random.Next() tiene una sobrecarga que acepta un valor mínimo y un valor máximo. Esta sobrecarga devuelve un número que es mayor o igual que el valor mínimo (inclusive) y menor que el valor máximo (exclusivo).

Me gustaría incluir todo el rango, incluido el valor máximo. En algunos casos, podría lograr esto simplemente agregando uno al valor máximo. Pero en este caso, el valor máximo puede ser int.MaxValue , y agregar uno a esto no lograría lo que quiero.

Entonces, ¿alguien sabe un buen truco para obtener un número aleatorio de int.MinValue a int.MaxValue , inclusive?

ACTUALIZAR:

Tenga en cuenta que el rango inferior puede ser int.MinValue pero también puede ser otra cosa. Si sé que siempre sería int.MinValue entonces el problema sería más simple.


Bueno, tengo un truco. No estoy seguro de describirlo como un "buen truco", pero siento que podría funcionar.

public static class RandomExtensions
{
    public static int NextInclusive(this Random rng, int minValue, int maxValue)
    {
        if (maxValue == int.MaxValue)
        {
            var bytes = new byte[4];
            rng.NextBytes(bytes);
            return BitConverter.ToInt32(bytes, 0);
        }
        return rng.Next(minValue, maxValue + 1);
    }
}

Entonces, básicamente, un método de extensión que simplemente generará cuatro bytes si el límite superior es int.MaxValue y se convierte en int , de lo contrario, simplemente use la sobrecarga estándar Next(int, int) .

Tenga en cuenta que si maxValue es int.MaxValue , ignorará minValue . Supongo que no tomé en cuenta eso ...


En realidad, es interesante que esta no sea la implementación de Random.Next (int, int), porque puede derivar el comportamiento exclusivo del comportamiento inclusivo, pero no al revés.

public static class RandomExtensions
{
    private const long IntegerRange = (long)int.MaxValue - int.MinValue;

    public static int NextInclusive(this Random random, int minValue, int maxValue)
    {
        if (minValue > maxValue)
        {
            throw new ArgumentOutOfRangeException(nameof(minValue));
        }

        var buffer = new byte[4];
        random.NextBytes(buffer);
        var a = BitConverter.ToInt32(buffer, 0);
        var b = a - (long)int.MinValue;
        var c = b * (1.0 / IntegerRange);
        var d = c * ((long)maxValue - minValue + 1);
        var e = (long)d + minValue;
        return (int)e;
    }
}
new Random(0).NextInclusive(int.MaxValue - 1, int.MaxValue); // returns int.MaxValue
new Random(1).NextInclusive(int.MaxValue - 1, int.MaxValue); // returns int.MaxValue - 1
new Random(0).NextInclusive(int.MinValue, int.MinValue + 1); // returns int.MinValue + 1
new Random(1).NextInclusive(int.MinValue, int.MinValue + 1); // returns int.MinValue
new Random(-451732719).NextInclusive(int.MinValue, int.MaxValue); // returns int.MinValue
new Random(-394328071).NextInclusive(int.MinValue, int.MaxValue); // returns int.MaxValue

Este método puede darle un número entero aleatorio dentro de cualquier límite entero. Si el límite máximo es menor que int.MaxValue, entonces usa el Random.Next ordinario (Int32, Int32) pero al agregar 1 al límite superior para incluir su valor. Si no, pero con un límite inferior mayor que int.MinValue, reduce el límite inferior con 1 para cambiar el rango 1 menos que agrega 1 al resultado. Finalmente, si ambos límites son int.MinValue e int.MaxValue, genera un entero aleatorio 'a' que es 0 o 1 con 50% de probabilidad de cada uno, luego genera otros dos enteros, el primero es entre int.MinValue y -1 inclusive, 2147483648 valores, y el segundo está entre 0 e int.MaxValue inclusive, 2147483648 valores también, y usándolos con el valor de 'a' selecciona un número entero con probabilidad totalmente igual.

private int RandomInclusive(int min, int max)
{
    if (max < int.MaxValue)
        return Random.Next(min, max + 1);
    if (min > int.MinValue)
        return Random.Next(min - 1, max) + 1;
    int a = Random.Next(2);
    return Random.Next(int.MinValue, 0) * a + (Random.Next(-1, int.MaxValue) + 1) * (1 - a);
}

Funcionara para ti?

int random(Random rnd, int min, int max) 
{
    return Convert.ToInt32(rnd.NextDouble() * (max - min) + min);
}

La implementación interna de Random.Next(int minValue, int maxValue) genera dos muestras para grandes rangos, como el rango entre Int32.MinValue e Int32.MaxValue . Para el método NextInclusive tuve que usar otro rango grande Next , totalizando cuatro muestras. Por lo tanto, el rendimiento debe ser comparable con la versión que llena un búfer con 4 bytes (una muestra por byte).

public static class RandomExtensions
{
    public static int NextInclusive(this Random random, int minValue, int maxValue)
    {
        if (maxValue == Int32.MaxValue)
        {
            if (minValue == Int32.MinValue)
            {
                var value1 = random.Next(Int32.MinValue, Int32.MaxValue);
                var value2 = random.Next(Int32.MinValue, Int32.MaxValue);
                return value1 < value2 ? value1 : value1 + 1;
            }
            return random.Next(minValue - 1, Int32.MaxValue) + 1;
        }
        return random.Next(minValue, maxValue + 1);
    }

}

Algunos resultados:

new Random(0).NextInclusive(int.MaxValue - 1, int.MaxValue); // returns int.MaxValue
new Random(1).NextInclusive(int.MaxValue - 1, int.MaxValue); // returns int.MaxValue - 1
new Random(0).NextInclusive(int.MinValue, int.MinValue + 1); // returns int.MinValue + 1
new Random(1).NextInclusive(int.MinValue, int.MinValue + 1); // returns int.MinValue
new Random(24917099).NextInclusive(int.MinValue, int.MaxValue); // returns int.MinValue
var random = new Random(784288084);
random.NextInclusive(int.MinValue, int.MaxValue);
random.NextInclusive(int.MinValue, int.MaxValue); // returns int.MaxValue

Actualización: mi implementación tiene un rendimiento mediocre para el mayor rango posible ( Int32.MinValue - Int32.MaxValue ), por lo que se me ocurrió una nueva que es 4 veces más rápida. Produce alrededor de 22,000,000 números aleatorios por segundo en mi máquina. No creo que pueda ser más rápido que eso.

public static int NextInclusive(this Random random, int minValue, int maxValue)
{
    if (maxValue == Int32.MaxValue)
    {
        if (minValue == Int32.MinValue)
        {
            var value1 = random.Next() % 0x10000;
            var value2 = random.Next() % 0x10000;
            return (value1 << 16) | value2;
        }
        return random.Next(minValue - 1, Int32.MaxValue) + 1;
    }
    return random.Next(minValue, maxValue + 1);
}

Algunos resultados:

new Random(0).NextInclusive(int.MaxValue - 1, int.MaxValue); // = int.MaxValue
new Random(1).NextInclusive(int.MaxValue - 1, int.MaxValue); // = int.MaxValue - 1
new Random(0).NextInclusive(int.MinValue, int.MinValue + 1); // = int.MinValue + 1
new Random(1).NextInclusive(int.MinValue, int.MinValue + 1); // = int.MinValue
new Random(1655705829).NextInclusive(int.MinValue, int.MaxValue); // = int.MaxValue
var random = new Random(1704364573);
random.NextInclusive(int.MinValue, int.MaxValue);
random.NextInclusive(int.MinValue, int.MaxValue);
random.NextInclusive(int.MinValue, int.MaxValue); // = int.MinValue

Puede agregar 1 al número generado aleatoriamente para que siga siendo aleatorio y cubra un entero de rango completo.

public static class RandomExtension
{
    public static int NextInclusive(this Random random, int minValue, int maxValue)
    {
        var randInt = random.Next(minValue, maxValue);
        var plus = random.Next(0, 2);

        return randInt + plus;
    }
}

Puedes probar esto. Un poco hacky pero puede obtener ambos min y max inclusive.

static void Main(string[] args)
        {
            int x = 0;

            var r = new Random();

            for (var i = 0; i < 32; i++)
            {
                x = x | (r.Next(0, 2) << i);

            }

            Console.WriteLine(x);
            Console.ReadKey();
        }

Sin conversión, sin long , se tienen en cuenta todos los casos límite, el mejor rendimiento.

static class RandomExtension
{
    private static readonly byte[] bytes = new byte[sizeof(int)];

    public static int InclusiveNext(this Random random, int min, int max)
    {
        if (max < int.MaxValue)
            // can safely increase 'max'
            return random.Next(min, max + 1);

        // now 'max' is definitely 'int.MaxValue'
        if (min > int.MinValue)
            // can safely decrease 'min'
            // so get ['min' - 1, 'max' - 1]
            // and move it to ['min', 'max']
            return random.Next(min - 1, max) + 1;

        // now 'max' is definitely 'int.MaxValue'
        // and 'min' is definitely 'int.MinValue'
        // so the only option is
        random.NextBytes(bytes);
        return BitConverter.ToInt32(bytes, 0);
    }
}

Sugeriría usar System.Numerics.BigInteger esta manera:

class InclusiveRandom
{
    private readonly Random rnd = new Random();

    public byte Next(byte min, byte max) => (byte)NextHelper(min, max);
    public sbyte Next(sbyte min, sbyte max) => (sbyte)NextHelper(min, max);
    public short Next(short min, short max) => (short)NextHelper(min, max);
    public ushort Next(ushort min, ushort max) => (ushort)NextHelper(min, max);
    public int Next(int min, int max) => (int)NextHelper(min, max);
    public uint Next(uint min, uint max) => (uint)NextHelper(min, max);
    public long Next(long min, long max) => (long)NextHelper(min, max);
    public ulong Next(ulong min, ulong max) => (ulong)NextHelper(min, max);

    private BigInteger NextHelper(BigInteger min, BigInteger max)
    {
        if (max <= min)
            throw new ArgumentException($"max {max} should be greater than min {min}");

        return min + RandomHelper(max - min);
    }

    private BigInteger RandomHelper(BigInteger bigInteger)
    {
        byte[] bytes = bigInteger.ToByteArray();
        BigInteger random;

        do
        {
            rnd.NextBytes(bytes);
            bytes[bytes.Length - 1] &= 0x7F;
            random = new BigInteger(bytes);
        } while (random > bigInteger);

        return random;
    }
}

Lo probé con sbyte .

var rnd = new InclusiveRandom();
var frequency = Enumerable.Range(sbyte.MinValue, sbyte.MaxValue - sbyte.MinValue + 1).ToDictionary(i => (sbyte)i, i => 0ul);
var count = 100000000;
for (var i = 0; i < count; i++)
    frequency[rnd.Next(sbyte.MinValue, sbyte.MaxValue)]++;
foreach (var i in frequency)
    chart1.Series[0].Points.AddXY(i.Key, (double)i.Value / count);

chart1.ChartAreas[0].AxisY.StripLines
    .Add(new StripLine { Interval = 0, IntervalOffset = 1d / 256, StripWidth = 0.0003, BackColor = Color.Red });

La distribución está bien.





random