สุ่มตัวเลขระหว่าง 2 ตัวเลขสองตัว


156

เป็นไปได้ไหมที่จะสร้างตัวเลขสุ่มระหว่าง 2 doubles?

ตัวอย่าง:

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

จากนั้นฉันจะเรียกมันว่า:

double result = GetRandomNumber(1.23, 5.34);

ความคิดใด ๆ ที่จะได้รับการชื่นชม.

คำตอบ:


329

ใช่.

Random.NextDouble คืนค่าสองเท่าระหว่าง 0 และ 1 จากนั้นคุณคูณด้วยตามช่วงที่คุณต้องการ (ความแตกต่างระหว่างค่าสูงสุดและค่าต่ำสุด) จากนั้นเพิ่มค่านั้นลงในฐาน (ขั้นต่ำ)

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

รหัสจริงควรมีการสุ่มเป็นสมาชิกแบบคงที่ วิธีนี้จะช่วยประหยัดค่าใช้จ่ายในการสร้างตัวสร้างหมายเลขสุ่มและจะช่วยให้คุณโทรหา GetRandomNumber ได้บ่อยมาก เนื่องจากเรากำลังเริ่มต้น RNG ใหม่ด้วยการโทรทุกครั้งหากคุณโทรเร็วพอที่เวลาของระบบจะไม่เปลี่ยนแปลงระหว่างการโทร RNG จะได้รับการเพาะด้วยการประทับเวลาเดียวกันที่แน่นอนและสร้างกระแสสุ่มหมายเลขเดียวกัน


33
เพียงแค่ดูว่าคุณเรียก GetRandomNumber () ในวงในขณะที่มันจะสร้างค่าเดียวกันกว่าและมากกว่า
จอห์น Rasch

13
อาจเป็นวิธีการขยายที่ดีpublic double GetRandomNumber(this Random random, double minimum, double maximum) {...}
จอห์นนี่ 5

5
โปรดทราบว่าเนื่องจาก Random.NextDouble จะไม่ส่งคืน 1.0 ฟังก์ชันนี้จะไม่เท่ากับจำนวนสูงสุด
แมทธิว

6
ฉันแนะนำให้ใส่เป็นแบบสุ่มใหม่ ((int) DateTime.Now.Ticks) สิ่งนี้ควรทำให้ "สุ่มมากขึ้น" แม้ในลูปด่วน
Daniel Skowroński

5
สิ่งนี้ไม่ทำงานหากค่า min เป็น double.MinValue และค่า max คือ double.MaxValue ข้อเสนอแนะเกี่ยวกับวิธีจัดการกรณีการใช้งานนี้?
ยีน S

40

Johnny5 แนะนำให้สร้างวิธีส่วนขยาย นี่คือตัวอย่างโค้ดที่สมบูรณ์ยิ่งขึ้นซึ่งแสดงวิธีที่คุณสามารถทำได้:

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

ตอนนี้คุณสามารถเรียกมันราวกับว่ามันเป็นวิธีการในRandomชั้นเรียน:

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

โปรดทราบว่าคุณไม่ควรสร้างRandomวัตถุใหม่จำนวนมากในการวนซ้ำเพราะจะทำให้มีโอกาสที่คุณจะได้รับค่าเดียวกันหลายครั้งในแถว หากคุณต้องการตัวเลขสุ่มจำนวนมากให้สร้างอินสแตนซ์หนึ่งRandomและใช้ซ้ำ


9

ระวัง: หากคุณกำลังสร้างrandomลูปภายในเช่นfor(int i = 0; i < 10; i++)อย่าใส่การnew Random()ประกาศในลูป

จากMSDN :

การสร้างตัวเลขสุ่มเริ่มต้นจากค่าเมล็ด หากใช้เมล็ดเดียวกันซ้ำหลายครั้งจะมีการสร้างตัวเลขชุดเดียวกัน วิธีหนึ่งในการสร้างลำดับที่แตกต่างกันคือการทำให้ค่าของเมล็ดขึ้นอยู่กับเวลา ตามค่าเริ่มต้นคอนสตรัคเตอร์แบบไม่มีพารามิเตอร์ของคลาสสุ่มจะใช้นาฬิการะบบเพื่อสร้างมูลค่าเมล็ด ...

ดังนั้นตามข้อเท็จจริงนี้ให้ทำสิ่งที่:

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

การทำเช่นนี้เป็นการรับประกันว่าคุณจะได้รับค่าสองเท่าที่แตกต่างกัน


8

วิธีที่ง่ายที่สุดจะสร้างตัวเลขสุ่มระหว่าง 0 กับความแตกต่างของตัวเลขสองตัว จากนั้นเพิ่มตัวเลขสองตัวที่น้อยลงในผลลัพธ์


3

คุณสามารถใช้รหัสเช่นนี้:

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

2

คุณสามารถทำได้:

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

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

2

ฉันมาช้าไปงานปาร์ตี้ แต่ฉันต้องใช้โซลูชันทั่วไปและปรากฎว่าไม่มีวิธีใดที่สามารถตอบสนองความต้องการของฉันได้

ทางออกที่ยอมรับได้ดีสำหรับช่วงขนาดเล็ก อย่างไรก็ตามmaximum - minimumอาจเป็นระยะอนันต์ ดังนั้นเวอร์ชันที่แก้ไขอาจเป็นเวอร์ชันนี้:

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

นี้จะสร้างตัวเลขสุ่มอย่างแม้กระทั่งระหว่างและdouble.MinValue double.MaxValueแต่นี่เป็นการแนะนำ "ปัญหา" อื่นซึ่งนำเสนออย่างดีในโพสต์นี้ : ถ้าเราใช้ช่วงใหญ่เช่นนั้นค่าอาจดูเหมือน "ผิดธรรมชาติ" ตัวอย่างเช่นหลังจากสร้าง 10,000 คู่สุ่มระหว่าง 0 และdouble.MaxValueค่าทั้งหมดอยู่ระหว่าง 2.9579E + 304 และ 1.7976E + 308

ดังนั้นฉันจึงสร้างเวอร์ชันอื่นซึ่งสร้างตัวเลขในสเกลลอการิทึม:

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

การทดสอบบางอย่าง:

นี่คือผลการเรียงลำดับของการสร้าง 10,000 เลขสุ่มระหว่าง 0 ถึงDouble.MaxValueทั้งสองกลยุทธ์ ผลลัพธ์จะปรากฏขึ้นโดยใช้มาตราส่วนลอการิทึม:

0..Double.MaxValue

แม้ว่าค่าสุ่มเชิงเส้นดูเหมือนว่าจะผิดในครั้งแรกที่สถิติแสดงให้เห็นว่าไม่มีของพวกเขา "ดีกว่า" อื่น ๆ : แม้กลยุทธ์เชิงเส้นมีการกระจายที่สม่ำเสมอและความแตกต่างเฉลี่ยระหว่างค่าค่อนข้างเหมือนกันมากกับทั้งสองกลยุทธ์ .

การเล่นกับช่วงที่แตกต่างกันแสดงให้ฉันเห็นว่ากลยุทธ์เชิงเส้นต้องเป็น "สติ" ที่มีช่วงระหว่าง 0 และushort.MaxValueด้วยค่าต่ำสุด "สมเหตุสมผล" ที่ 10.78294704 (สำหรับulongช่วงค่าต่ำสุดคือ 3.03518E + 15 int;: 353341) สิ่งเหล่านี้เป็นผลลัพธ์เดียวกันของทั้งสองกลยุทธ์ที่แสดงด้วยเครื่องชั่งที่แตกต่างกัน:

0..UInt16.MaxValue


แก้ไข:

เมื่อเร็ว ๆ นี้ฉันทำให้ห้องสมุดโอเพ่นซอร์สของฉันรู้สึกอิสระที่จะเห็นRandomExtensions.NextDoubleวิธีการตรวจสอบที่สมบูรณ์


1

เกิดอะไรขึ้นถ้าค่าใดค่าหนึ่งเป็นลบ จะไม่เป็นความคิดที่ดีกว่า:

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

5
ฉันคิดว่าMath.Abs()มันซ้ำซ้อน เมื่อคุณมั่นใจmin >= maxแล้วmax - minต้องเป็นหมายเลขที่ไม่เป็นลบอยู่ดี
Brian Reischl

1

หากคุณต้องการตัวเลขสุ่มในช่วง [ 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)

ใช้แทน:

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
จุดดี แต่ข้อเสนอนี้ส่งผลให้เกิดการแจกแจงแบบเบ้เหมือนในตัวอย่างที่สองที่นี่stackoverflow.com/a/3365388/3922292
Piotr Falkowski

0

เกี่ยวกับการสร้างตัวเลขสุ่มที่เหมือนกันถ้าคุณเรียกมันในลูปโซลูชันที่ดีก็คือการประกาศออบเจกต์ Random () ใหม่นอกลูปเป็นตัวแปรกลาง

ขอให้สังเกตว่าคุณต้องประกาศอินสแตนซ์ของคลาส Random ภายนอกฟังก์ชัน GetRandomInt ถ้าคุณต้องการเรียกใช้งานแบบวนซ้ำ

“ ทำไมถึงเป็นอย่างนี้?” คุณถาม.

คลาส Random จะสร้างตัวเลขสุ่มหลอกจริงด้วย“ seed” สำหรับตัวสุ่มที่เป็นเวลาของระบบ หากลูปของคุณเร็วพอเวลาของนาฬิการะบบจะไม่แตกต่างจากตัวสุ่มและแต่ละอินสแตนซ์ใหม่ของคลาสสุ่มจะเริ่มต้นด้วยเมล็ดเดียวกันและให้หมายเลขสุ่มหลอกเดียวกัน

แหล่งที่มาอยู่ที่นี่: http://www.whypad.com/posts/csharp-get-a-random-number-between-x-and-y/412/


สิ่งนี้เหมาะสำหรับความคิดเห็นมากกว่าเพราะไม่ได้พยายามตอบคำถามจริงของ OP
b1nary.atr0phy

0

ใช้การสุ่มแบบคงที่หรือตัวเลขมีแนวโน้มที่จะทำซ้ำในแน่น / เร็วลูปเนื่องจากนาฬิการะบบที่พวกเขา

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

ดูเพิ่มเติมที่: https://docs.microsoft.com/en-us/dotnet/api/system.random?view=netframework-4.8


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.