Losowa liczba między 2 podwójnymi liczbami


156

Czy można wygenerować losową liczbę między 2 podwójnymi?

Przykład:

public double GetRandomeNumber(double minimum, double maximum)
{
    return Random.NextDouble(minimum, maximum) 
}

Następnie nazywam to następującymi słowami:

double result = GetRandomNumber(1.23, 5.34);

Wszelkie myśli będą mile widziane.

Odpowiedzi:


329

Tak.

Random.NextDouble zwraca podwójną wartość między 0 a 1. Następnie mnożysz to przez zakres, do którego chcesz wejść (różnica między maksimum a minimum), a następnie dodajesz to do podstawy (minimum).

public double GetRandomNumber(double minimum, double maximum)
{ 
    Random random = new Random();
    return random.NextDouble() * (maximum - minimum) + minimum;
}

Prawdziwy kod powinien mieć losowy element statyczny. Pozwoli to zaoszczędzić koszty tworzenia generatora liczb losowych i umożliwi bardzo częste wywoływanie GetRandomNumber. Ponieważ inicjalizujemy nowy RNG przy każdym wywołaniu, jeśli zadzwonisz wystarczająco szybko, aby czas systemowy nie zmienił się między wywołaniami, RNG zostanie zapełniony dokładnie tym samym znacznikiem czasu i wygeneruje ten sam strumień liczb losowych.


33
Po prostu uważaj, jeśli wywołasz GetRandomNumber () w pętli, ponieważ będzie generować tę samą wartość w kółko
John Rasch

13
To może być niezła metoda rozszerzenia,public double GetRandomNumber(this Random random, double minimum, double maximum) {...}
Johnny5

5
Należy pamiętać, że ponieważ Random.NextDouble nigdy nie zwraca 1,0, ta funkcja również nigdy nie będzie równa maksymalnej liczbie.
Matthew

6
Proponuję umieścić jako ziarno nowy Random ((int) DateTime.Now.Ticks), co powinno uczynić go „bardziej losowym” nawet w szybkich pętlach.
Daniel Skowroński

5
To nie działa, jeśli wartość minimalna jest równa double.MinValue, a wartość maksymalna to double.MaxValue. Jakieś sugestie, jak obsłużyć ten przypadek użycia?
Gene S

40

Johnny5 zasugerował utworzenie metody rozszerzającej. Oto pełniejszy przykład kodu pokazujący, jak możesz to zrobić:

public static class RandomExtensions
{
    public static double NextDouble(
        this Random random,
        double minValue,
        double maxValue)
    {
        return random.NextDouble() * (maxValue - minValue) + minValue;
    }
}

Teraz możesz to nazwać tak, jakby to była metoda w Randomklasie:

Random random = new Random();
double value = random.NextDouble(1.23, 5.34);

Zwróć uwagę, że nie powinieneś tworzyć wielu nowych Randomobiektów w pętli, ponieważ spowoduje to prawdopodobieństwo uzyskania tej samej wartości wiele razy z rzędu. Jeśli potrzebujesz wielu liczb losowych, utwórz jedną instancję Randomi wykorzystaj ją ponownie.


9

Uważaj: jeśli generujesz randomwewnętrzną pętlę, jak na przykład for(int i = 0; i < 10; i++), nie umieszczaj new Random()deklaracji wewnątrz pętli.

Z MSDN :

Generowanie liczb losowych rozpoczyna się od wartości początkowej. Jeśli to samo ziarno jest używane wielokrotnie, generowana jest ta sama seria liczb. Jednym ze sposobów tworzenia różnych sekwencji jest uzależnienie wartości początkowej od czasu, tworząc w ten sposób inną serię z każdym nowym wystąpieniem Random. Domyślnie konstruktor bezparametrowy klasy Random używa zegara systemowego do generowania wartości początkowej ...

Więc opierając się na tym fakcie, zrób coś takiego:

var random = new Random();

for(int d = 0; d < 7; d++)
{
    // Actual BOE
    boes.Add(new LogBOEViewModel()
    {
        LogDate = criteriaDate,
        BOEActual = GetRandomDouble(random, 100, 1000),
        BOEForecast = GetRandomDouble(random, 100, 1000)
    });
}

double GetRandomDouble(Random random, double min, double max)
{
     return min + (random.NextDouble() * (max - min));
}

W ten sposób masz gwarancję, że otrzymasz różne podwójne wartości.


8

Najprostsze podejście po prostu wygeneruje liczbę losową z przedziału od 0 do różnicy tych dwóch liczb. Następnie dodaj do wyniku mniejszą z dwóch liczb.


3

Możesz użyć takiego kodu:

public double getRandomNumber(double minimum, double maximum) {
    return minimum + randomizer.nextDouble() * (maximum - minimum);
}

2

Możesz to zrobić:

public class RandomNumbers : Random
{
    public RandomNumbers(int seed) : base(seed) { }

    public double NextDouble(double minimum, double maximum)
    {
        return base.NextDouble() * (maximum - minimum) + minimum;
    }
}

2

Trochę spóźniłem się na imprezę, ale musiałem wdrożyć ogólne rozwiązanie i okazało się, że żadne z rozwiązań nie jest w stanie zaspokoić moich potrzeb.

Przyjęte rozwiązanie jest dobre dla małych zasięgów; jednakże maximum - minimummoże to być nieskończoność dla dużych zakresów. Tak więc poprawioną wersją może być ta wersja:

public static double NextDoubleLinear(this Random random, double minValue, double maxValue)
{
    // TODO: some validation here...
    double sample = random.NextDouble();
    return (maxValue * sample) + (minValue * (1d - sample));
}

To ładnie generuje liczby losowe, nawet między double.MinValuea double.MaxValue. Ale to wprowadza kolejny „problem”, który ładnie przedstawiono w tym poście : jeśli użyjemy tak dużych zakresów, wartości mogą wydawać się zbyt „nienaturalne”. Na przykład po wygenerowaniu 10 000 losowych podwójnych wartości z zakresu od 0 do double.MaxValuewszystkich wartości znajdowały się w przedziale od 2,9579E + 304 do 1,7976E + 308.

Stworzyłem więc również inną wersję, która generuje liczby w skali logarytmicznej:

public static double NextDoubleLogarithmic(this Random random, double minValue, double maxValue)
{
    // TODO: some validation here...
    bool posAndNeg = minValue < 0d && maxValue > 0d;
    double minAbs = Math.Min(Math.Abs(minValue), Math.Abs(maxValue));
    double maxAbs = Math.Max(Math.Abs(minValue), Math.Abs(maxValue));

    int sign;
    if (!posAndNeg)
        sign = minValue < 0d ? -1 : 1;
    else
    {
        // if both negative and positive results are expected we select the sign based on the size of the ranges
        double sample = random.NextDouble();
        var rate = minAbs / maxAbs;
        var absMinValue = Math.Abs(minValue);
        bool isNeg = absMinValue <= maxValue ? rate / 2d > sample : rate / 2d < sample;
        sign = isNeg ? -1 : 1;

        // now adjusting the limits for 0..[selected range]
        minAbs = 0d;
        maxAbs = isNeg ? absMinValue : Math.Abs(maxValue);
    }

    // Possible double exponents are -1022..1023 but we don't generate too small exponents for big ranges because
    // that would cause too many almost zero results, which are much smaller than the original NextDouble values.
    double minExponent = minAbs == 0d ? -16d : Math.Log(minAbs, 2d);
    double maxExponent = Math.Log(maxAbs, 2d);
    if (minExponent == maxExponent)
        return minValue;

    // We decrease exponents only if the given range is already small. Even lower than -1022 is no problem, the result may be 0
    if (maxExponent < minExponent)
        minExponent = maxExponent - 4;

    double result = sign * Math.Pow(2d, NextDoubleLinear(random, minExponent, maxExponent));

    // protecting ourselves against inaccurate calculations; however, in practice result is always in range.
    return result < minValue ? minValue : (result > maxValue ? maxValue : result);
}

Niektóre testy:

Oto posortowane wyniki generowania 10000 losowych liczb podwójnych z przedziału od 0 i Double.MaxValueprzy obu strategiach. Wyniki wyświetlane są w skali logarytmicznej:

0..Double.MaxValue

Chociaż liniowe wartości losowe wydają się na pierwszy rzut oka błędne, statystyki pokazują, że żadna z nich nie jest „lepsza” od drugiej: nawet strategia liniowa ma równomierny rozkład, a średnia różnica między wartościami jest prawie taka sama w przypadku obu strategii .

Gra z różnymi zakresami pokazała mi, że strategia liniowa staje się „rozsądna” w zakresie od 0 ushort.MaxValuedo „rozsądnej” wartości minimalnej 10,78294704 (dla ulongzakresu minimalna wartość to 3,03518E + 15 int;: 353341). Oto te same wyniki obu strategii wyświetlane w różnych skalach:

0..UInt16.MaxValue


Edytować:

Niedawno uczyniłem moje biblioteki open source, zapraszam do obejrzenia RandomExtensions.NextDoublemetody z pełną walidacją.


1

A co jeśli jedna z wartości jest ujemna? Nie byłoby lepszym pomysłem:

double NextDouble(double min, double max)
{
       if (min >= max)
            throw new ArgumentOutOfRangeException();    
       return random.NextDouble() * (Math.Abs(max-min)) + min;
}

5
Myślę, że Math.Abs()jest zbędny. Ponieważ zapewniłeś to min >= max, i tak max - minmusi to być liczba nieujemna.
Brian Reischl

1

Jeśli potrzebujesz losowej liczby z zakresu [ double.MinValue; double.MaxValue]

// Because of:
double.MaxValue - double.MinValue == double.PositiveInfinity

// This will be equals to NaN or PositiveInfinity
random.NextDouble() * (double.MaxValue - double.MinValue)

Użyj zamiast tego:

public static class RandomExtensions
{
    public static double NextDoubleInMinMaxRange(this Random random)
    {
        var bytes = new byte[sizeof(double)];
        var value = default(double);
        while (true)
        {
            random.NextBytes(bytes);
            value = BitConverter.ToDouble(bytes, 0);
            if (!double.IsNaN(value) && !double.IsInfinity(value))
                return value;
        }
    }
}

2
Słuszna uwaga, ale ta propozycja skutkuje wypaczonym rozkładem, jak w drugim przykładzie tutaj stackoverflow.com/a/3365388/3922292
Piotr Falkowski

0

Jeśli chodzi o generowanie tej samej liczby losowej, jeśli wywołasz ją w pętli, fajnym rozwiązaniem jest zadeklarowanie nowego obiektu Random () poza pętlą jako zmiennej globalnej.

Zauważ, że musisz zadeklarować swoją instancję klasy Random poza funkcją GetRandomInt, jeśli zamierzasz to uruchomić w pętli.

"Dlaczego to?" ty pytasz.

Cóż, klasa Random w rzeczywistości generuje liczby pseudolosowe, a „ziarnem” randomizera jest czas systemowy. Jeśli twoja pętla jest wystarczająco szybka, zegar systemowy nie będzie się różnił od randomizera, a każda nowa instancja klasy Random będzie rozpoczynać się od tego samego ziarna i dawać tę samą pseudolosową liczbę.

Źródło jest tutaj: http://www.whypad.com/posts/csharp-get-a-random-number-between-x-and-y/412/


Bardziej nadaje się jako komentarz, ponieważ nawet nie próbuje odpowiedzieć na rzeczywiste pytanie OP.
b1nary.atr0phy

0

Użyj statycznego losowania lub liczby mają tendencję do powtarzania się w ciasnych / szybkich pętlach, ponieważ zegar systemowy je rozdziela.

public static class RandomNumbers
{
    private static Random random = new Random();
    //=-------------------------------------------------------------------
    // double between min and the max number
    public static double RandomDouble(int min, int max) 
    {
        return (random.NextDouble() * (max - min)) + min;
    }
    //=----------------------------------
    // double between 0 and the max number
    public static double RandomDouble(int max) 
    {
        return (random.NextDouble() * max);
    }
    //=-------------------------------------------------------------------
    // int between the min and the max number
    public static int RandomInt(int min, int max) 
    {   
        return random.Next(min, max + 1);
    }
    //=----------------------------------
    // int between 0 and the max number
    public static int RandomInt(int max) 
    {
        return random.Next(max + 1);
    }
    //=-------------------------------------------------------------------
 }

Zobacz też: https://docs.microsoft.com/en-us/dotnet/api/system.random?view=netframework-4.8


-1
Random random = new Random();

double NextDouble(double minimum, double maximum)
{  

    return random.NextDouble()*random.Next(minimum,maximum);

}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.