c# - Número aleatorio entre int.MinValue y int.MaxValue, inclusive
.net random (14)
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.
¿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();
}
}
}
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 ...
Divida los rangos en dos y compense el
MaxValue
:
r.Next(2) == 0 ? r.Next(int.MinValue, 0) : (1 + r.Next(-1, int.MaxValue))
Si hacemos los rangos de igual tamaño, podemos obtener el mismo resultado con diferentes matemáticas.
Aquí confiamos en el hecho de que
int.MinValue = -1 - int.MaxValue
:
r.Next(int.MinValue, 0) - (r.Next(2) == 0 ? 0 : int.MinValue)
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);
}
Esto está garantizado para trabajar con valores negativos y no negativos:
public static int NextIntegerInclusive(this Random r, int min_value, int max_value)
{
if (max_value < min_value)
{
throw new InvalidOperationException("max_value must be greater than min_value.");
}
long offsetFromZero =(long)min_value; // e.g. -2,147,483,648
long bound = (long)max_value; // e.g. 2,147,483,647
bound -= offsetFromZero; // e.g. 4,294,967,295 (uint.MaxValue)
bound += Math.Sign(bound); // e.g. 4,294,967,296 (uint.MaxValue + 1)
return (int) (Math.Round(r.NextDouble() * bound) + offsetFromZero); // e.g. -2,147,483,648 => 2,147,483,647
}
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
No puede usar
Random.Next()
para lograr lo que desea, porque no puede corresponder la secuencia de
N
números a
N+1
y no perder uno :).
Período.
Pero puede usar
Random.NextDouble()
, que devuelve un resultado doble:
0 <= x < 1
aka
[0, 1)
entre 0, donde
[
es signo inclusivo y
)
exclusivo
¿Cómo correspondemos N números a [0, 1)?
Debe dividir
[0, 1)
en N segmentos iguales:
[0, 1/N)
,
[1/N, 2/N)
, ...
[N-1/N, 1)
Y aquí es donde se vuelve importante que un borde sea inclusivo y otro exclusivo: ¡todos los N segmentos son absolutamente iguales!
Aquí está mi código: lo hice como un simple programa de consola.
class Program
{
private static Int64 _segmentsQty;
private static double _step;
private static Random _random = new Random();
static void Main()
{
InclusiveRandomPrep();
for (int i = 1; i < 20; i++)
{
Console.WriteLine(InclusiveRandom());
}
Console.ReadLine();
}
public static void InclusiveRandomPrep()
{
_segmentsQty = (Int64)int.MaxValue - int.MinValue;
_step = 1.0 / _segmentsQty;
}
public static int InclusiveRandom()
{
var randomDouble = _random.NextDouble();
var times = randomDouble / _step;
var result = (Int64)Math.Floor(times);
return (int)result + 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();
}
Según tengo entendido, desea que Random ponga un valor entre -2.147.483.648 y +2.147.483.647. Pero el problema es aleatorio dado que esos valores solo darán valores de -2.147.483.648 a +2.147.483.64 6 , ya que el máximo es exclusivo.
Opción 0: quítatelo y aprende a prescindir de él
Douglas Adams no era un programador AFAIK, pero tiene algunos buenos consejos para nosotros: "La tecnología involucrada en hacer que algo sea invisible es tan infinitamente compleja que novecientos noventa y nueve mil novecientos noventa y nueve millones novecientos noventa -nueve mil novecientos noventa y nueve veces de un billón es mucho más simple y efectivo simplemente quitar la cosa y prescindir de ella ".
Este podría ser un caso así.
Opción 1: ¡Necesitamos un Random más grande!
Random.Next usa Int32 como argumento.
Una opción que puedo pensar sería usar una función aleatoria diferente que puede tomar el siguiente nivel más alto de enteros (Int64) como entrada.
Un Int32 se convierte implícitamente en un Int64.
Int64 Number = Int64(Int32.MaxValue)+1;
Pero afaik, tendrías que salir de las bibliotecas .NET para hacer esto. En ese punto, también podrías buscar un Aleatorio que incluya el Máx.
Pero creo que hay una razón matemática por la que tuvo que excluir un valor.
Opción 2: rodar más
Otra forma es usar dos llamadas de Aleatorio, cada una para la mitad del rango, y luego agregarlas.
Number1 = rng.Next(-2.147.483.648, 0);
Number2 = rng.Next(0, 2.147.483.647);
resut = Number1 + Number2;
Sin embargo, estoy 90% seguro de que arruinará la distribución de randon. Mi experiencia en P&P RPG me dio algo de experiencia con las posibilidades de dados y sé que, de hecho, tirar 2 dados (o las mismas 2 veces) te dará una distribución de resultados muy diferente a un dado específico. Si no necesita esta distribución aleatoria, esa es una opción. Pero si no te importa demasiado la distribución, vale la pena comprobarlo.
Opción 3: ¿necesita la gama completa? ¿O simplemente te interesa que min y max estén en él?
Supongo que está haciendo alguna forma de prueba y necesita que Int.MaxValue e Int.MinValue estén en el rango. ¿Pero también necesita todos los valores intermedios , o podría prescindir de uno de ellos? Si tiene que perder un valor, ¿preferiría perder 4 en lugar de Int.MaxValue?
Number = rng.Next(Int.MinValue, Int.MaxValue);
if(Number > 3)
Number = Number +1;
un código como este le proporcionaría todos los números entre MinValueand y Maxvalue, excepto 4 . Pero en la mayoría de los casos, el código que puede manejar 3 y 5 también puede manejar 4. No hay necesidad de probar explícitamente 4.
Por supuesto que asumes 4 no es un número de prueba importante que deba ejecutarse (evité 1 y 0 por esas razones). También puede decidir el número para "omitir" aleatoriamente:
skipAbleNumber = rng.Next(Int.MinValue +1, Int.MaxValue);
Y luego use
> skipAbleNumber
lugar de
> 4
.
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.