ทำไมค่าสุ่มนี้จึงมีการแจกแจง 25/75 แทนที่จะเป็น 50/50


139

แก้ไข:ดังนั้นโดยทั่วไปสิ่งที่ฉันพยายามที่จะเขียนเป็นกัญชา 1 doubleบิตสำหรับ

ฉันต้องการที่จะแมปdoubleไปtrueหรือfalseมีโอกาสที่ 50/50 สำหรับที่ฉันเขียนโค้ดที่เลือกตัวเลขสุ่ม(เช่นเป็นตัวอย่างฉันต้องการใช้กับข้อมูลที่มี regularities และยังได้ผลลัพธ์ 50/50)ตรวจสอบบิตสุดท้ายและส่วนเพิ่มyหากเป็น 1 หรือnถ้าเป็น 0

แต่รหัสนี้อย่างต่อเนื่องส่งผลให้ใน 25% yและ n75% ทำไมมันไม่ 50/50 และทำไมการกระจายแบบแปลก ๆ แต่ตรงไปตรงมา (1/3)

public class DoubleToBoolean {
    @Test
    public void test() {

        int y = 0;
        int n = 0;
        Random r = new Random();
        for (int i = 0; i < 1000000; i++) {
            double randomValue = r.nextDouble();
            long lastBit = Double.doubleToLongBits(randomValue) & 1;
            if (lastBit == 1) {
                y++;
            } else {
                n++;
            }
        }
        System.out.println(y + " " + n);
    }
}

ตัวอย่างผลลัพธ์:

250167 749833

43
ฉันหวังว่าคำตอบจะเป็นสิ่งที่น่าสนใจเกี่ยวกับการสร้างจุดลอยตัวแบบสุ่มแทนที่จะเป็น "LCG มีเอนโทรปีต่ำในบิตต่ำ"
Sneftel

4
ฉันอยากรู้อยากเห็นมากอะไรคือจุดประสงค์ของ "แฮช 1 บิทสำหรับคู่" คืออะไร? ฉันไม่สามารถคิดถึงการใช้ข้อกำหนดที่ถูกต้องตามกฎหมายอย่างจริงจัง
corsiKa

3
@corsiKa ในการคำนวณทางเรขาคณิตมักจะมีสองกรณีที่เรากำลังมองหาที่จะเลือกจากสองคำตอบที่เป็นไปได้ (เช่นชี้ไปทางซ้ายหรือทางขวาของบรรทัด?) และบางครั้งมันจะแนะนำกรณีที่สามเลวลง (จุดคือ ขวาบนบรรทัด) แต่คุณมีคำตอบที่ใช้ได้เพียงสองคำตอบดังนั้นคุณจึงต้องสุ่มเลือกคำตอบที่มีอยู่ในกรณีนั้น วิธีที่ดีที่สุดที่ฉันคิดได้คือใช้แฮช 1 บิตของหนึ่งในสองค่าที่กำหนด (จำไว้ว่านั่นคือการคำนวณเชิงเรขาคณิต
gvlasov

2
@corsiKa (ความคิดเห็นแบ่งออกเป็นสองเพราะมันยาวเกินไป) เราสามารถเริ่มต้นที่สิ่งที่ง่ายกว่าเช่นdoubleValue % 1 > 0.5แต่มันจะหยาบเกินไปเนื่องจากมันสามารถแนะนำระเบียบที่มองเห็นได้ในบางกรณี (ค่าทั้งหมดอยู่ในช่วงความยาว 1) ถ้ามันหยาบเกินไปคุณควรลองช่วงที่เล็กกว่านี้doubleValue % 1e-10 > 0.5e-10ไหม? ก็ใช่ และรับบิตสุดท้ายเป็นแฮชของ a doubleคือสิ่งที่เกิดขึ้นเมื่อคุณทำตามวิธีการนี้ไปจนจบด้วยโมดูโลที่เป็นไปได้น้อยที่สุด
gvlasov

1
@kmote แล้วคุณยังคงมีบิตที่มีนัยสำคัญน้อยที่สุดที่มีอคติน้อยและอีกบิตหนึ่งไม่ชดเชยมัน - ในความเป็นจริงมันยังมีอคติต่อศูนย์ (แต่น้อยกว่านั้น) ด้วยเหตุผลเดียวกันทั้งหมด ดังนั้นการกระจายจะประมาณ 50, 12.5, 25, 12.5 (lastbit & 3) == 0จะทำงานแม้ว่าแปลกตามที่เป็นอยู่
แฮโรลด์

คำตอบ:


165

เพราะ nextDouble ทำงานได้ดังนี้: ( แหล่งที่มา )

public double nextDouble()
{
    return (((long) next(26) << 27) + next(27)) / (double) (1L << 53);
}

next(x)ทำให้xบิตสุ่ม

ตอนนี้ทำไมเรื่องนี้? เพราะประมาณครึ่งหนึ่งของจำนวนที่สร้างขึ้นโดยส่วนแรก (ก่อนการหาร) มีค่าน้อยกว่า1L << 52และดังนั้นซิกนิฟิแคนด์ของพวกเขาจึงไม่เติมเต็ม 53 บิตที่มันสามารถเติมได้ซึ่งหมายความว่าบิตนัยสำคัญน้อยที่สุดของซิกนิฟิแคนด์


เนื่องจากจำนวนความสนใจที่ได้รับนี่เป็นคำอธิบายเพิ่มเติมเกี่ยวกับสิ่งที่เป็นdoubleใน Java (และภาษาอื่น ๆ อีกมากมาย) ดูเหมือนและทำไมมันถึงมีความสำคัญในคำถามนี้

โดยทั่วไปมีdoubleลักษณะเช่นนี้: (ที่มา )

เค้าโครงคู่

รายละเอียดที่สำคัญมากที่มองไม่เห็นในรูปภาพนี้คือตัวเลข "ปกติ" 1ซึ่งเศษส่วน 53 บิตเริ่มต้นด้วย 1 (โดยการเลือกเลขชี้กำลังเช่นนั้น) 1 นั้นจะถูกละเว้น นั่นคือเหตุผลที่รูปภาพแสดงเศษบิต 52 (ซิกนิฟิแคนด์) แต่มี 53 บิตในนั้น

ฟื้นฟูหมายความว่าถ้าในรหัสสำหรับnextDoubleบิต 53 มีการตั้งค่าที่บิตเป็นนัยชั้นนำ 1 และมันก็จะหายไปและอื่น ๆ 52 doubleบิตจะถูกคัดลอกแท้จริงของซิกที่เกิด หากไม่ได้ตั้งค่าบิตนั้นบิตที่เหลือจะต้องเลื่อนไปทางซ้ายจนกว่าจะตั้งค่า

โดยเฉลี่ยแล้วครึ่งหนึ่งของตัวเลขที่สร้างขึ้นจะตกอยู่ในกรณีที่ไม่มีการเปลี่ยนซิกนิฟิแคนด์ไปเลย (และประมาณครึ่งหนึ่งที่มี 0 เป็นบิตที่มีนัยสำคัญน้อยที่สุด) และอีกครึ่งหนึ่งจะถูกเลื่อนอย่างน้อย 1 ศูนย์) ดังนั้นบิตที่มีนัยสำคัญน้อยที่สุดจะเป็น 0 เสมอ

1: ไม่เสมอไปเห็นได้ชัดว่ามันไม่สามารถทำได้สำหรับศูนย์ซึ่งไม่มีสูงสุด 1 ตัวเลขเหล่านี้เรียกว่าหมายเลขปกติหรือตัวเลขย่อยดูวิกิพีเดีย: หมายเลขปกติ


16
ไชโย! สิ่งที่ฉันหวังไว้
Sneftel

3
@ แมทสมมุติว่าเป็นการเพิ่มประสิทธิภาพความเร็ว ทางเลือกคือการสร้างเลขชี้กำลังด้วยการกระจายเชิงเรขาคณิตและจากนั้น Manti ก็แยกกัน
Sneftel

7
@Matt: กำหนด "ดีที่สุด" random.nextDouble()โดยทั่วไปแล้วเป็นวิธีที่ "ดีที่สุด" สำหรับสิ่งที่ตั้งใจไว้ แต่คนส่วนใหญ่ไม่ได้พยายามแฮช 1 บิตจากการสุ่มสองครั้ง คุณกำลังมองหาการกระจายที่สม่ำเสมอความต้านทานต่อการเข้ารหัสหรืออะไร
StriplingWarrior

1
คำตอบนี้แสดงให้เห็นว่าถ้า OP ได้คูณจำนวนสุ่มด้วย 2 ^ 53 และตรวจสอบว่าจำนวนเต็มที่เกิดเป็นเลขคี่จะมีการแจกแจง 50/50
rici

4
@ The111 มันบอกว่าที่นี่ที่nextจะต้องส่งกลับintเพื่อที่จะสามารถมีเพียงถึง 32 บิตอยู่แล้ว
แฮโรลด์

48

จากเอกสาร :

เมธอด nextDouble ดำเนินการโดยคลาส Random ราวกับโดย:

public double nextDouble() {
  return (((long)next(26) << 27) + next(27))
      / (double)(1L << 53);
}

แต่มันก็ยังกล่าวถึงต่อไปนี้ (เหมืองเน้น):

[ใน Java เวอร์ชันก่อนหน้าผลลัพธ์ถูกคำนวณอย่างไม่ถูกต้องเป็น:

 return (((long)next(27) << 27) + next(27))
     / (double)(1L << 54);

สิ่งนี้อาจดูเหมือนจะเท่าเทียมกันถ้าไม่ดีขึ้น แต่ในความเป็นจริงมันแนะนำ nonuniformity ขนาดใหญ่เนื่องจากความเอนเอียงในการปัดเศษของจำนวนจุดลอยตัว: เป็นสามเท่าของโอกาสที่บิตลำดับต่ำของซิกนิฟิแคนด์จะเป็น 0 กว่านั้นจะเป็น 1 ! ความไม่เป็นเอกเทศนี้อาจไม่สำคัญในทางปฏิบัติมากนัก แต่เรามุ่งมั่นเพื่อความสมบูรณ์แบบ]

บันทึกนี้อยู่ที่นั่นตั้งแต่ Java 5 เป็นอย่างน้อย (เอกสารสำหรับ Java <= 1.4 อยู่หลัง loginwall ซึ่งไม่น่าตรวจสอบ) สิ่งนี้น่าสนใจเพราะเห็นได้ชัดว่าปัญหายังคงมีอยู่แม้ใน Java 8 บางทีรุ่น "แก้ไข" ไม่เคยถูกทดสอบ?


4
แปลก. ฉันเพิ่งทำซ้ำสิ่งนี้ใน Java 8
aioobe

1
ตอนนี้มันน่าสนใจเพราะฉันแค่แย้งว่าอคติยังคงใช้กับวิธีการใหม่ ฉันผิดหรือเปล่า?
แฮโรลด์

3
@ ฮาโรลด์: ไม่ฉันคิดว่าคุณพูดถูกและใครก็ตามที่พยายามแก้ไขอคตินี้อาจทำผิดพลาด
โทมัส

6
@harold เวลาที่จะส่งอีเมลไปยังพวก Java
Daniel

8
"บางทีเวอร์ชันที่แน่นอนไม่เคยถูกทดสอบ?" ที่จริงแล้วเมื่ออ่านสิ่งนี้ฉันคิดว่าหมอเกี่ยวกับปัญหาที่แตกต่าง หมายเหตุว่ามันกล่าวถึงการปัดเศษซึ่งแสดงให้เห็นว่าพวกเขาไม่ได้พิจารณา "สามครั้งเป็นโอกาส" จะมีปัญหาโดยตรง แต่ที่นำไปสู่การนี้เพื่อให้การกระจายไม่สม่ำเสมอเมื่อค่าที่มีความโค้งมน โปรดทราบว่าในคำตอบของฉันรายการค่าที่ฉันมีการกระจายอย่างสม่ำเสมอ แต่บิตลำดับต่ำตามที่แสดงในรูปแบบ IEEE ไม่เหมือนกัน ฉันคิดว่าปัญหาที่พวกเขาแก้ไขต้องเกี่ยวข้องกับความเท่าเทียมโดยรวมไม่ใช่ความสม่ำเสมอของบิตต่ำ
ajb

33

ผลลัพธ์นี้ไม่ทำให้ฉันแปลกใจเลยว่าจะแสดงจำนวนจุดลอยตัวอย่างไร สมมุติว่าเรามีจุดลอยตัวสั้นมากที่มีความแม่นยำเพียง 4 บิต หากเราต้องสร้างตัวเลขสุ่มระหว่าง 0 ถึง 1 กระจายอย่างสม่ำเสมอจะมี 16 ค่าที่เป็นไปได้:

0.0000
0.0001
0.0010
0.0011
0.0100
...
0.1110
0.1111

ถ้านั่นคือสิ่งที่พวกเขามองเข้าไปในเครื่องคุณสามารถทดสอบบิตต่ำเพื่อรับการกระจาย 50/50 อย่างไรก็ตามการลอยตัวของ IEEE นั้นมีพลังเป็น 2 เท่าของ mantissa; หนึ่งเขตข้อมูลในการลอยคือพลังของ 2 (บวกออฟเซ็ตคงที่) เลือกกำลังของ 2 เพื่อให้ส่วน "mantissa" เป็นตัวเลข> = 1.0 และ <2.0 เสมอ ซึ่งหมายความว่าในทางกลับกันตัวเลขอื่น ๆ ที่0.0000จะแสดงเช่นนี้:

0.0001 = 2^(-4) x 1.000
0.0010 = 2^(-3) x 1.000
0.0011 = 2^(-3) x 1.100
0.0100 = 2^(-2) x 1.000
... 
0.0111 = 2^(-2) x 1.110
0.1000 = 2^(-1) x 1.000
0.1001 = 2^(-1) x 1.001
...
0.1110 = 2^(-1) x 1.110
0.1111 = 2^(-1) x 1.111

( 1ก่อนหน้าจุดฐานสองเป็นค่าโดยนัยสำหรับ 32- และ 64- บิตลอยไม่มีการจัดสรรบิตเพื่อเก็บสิ่งนี้1จริงๆ)

แต่การดูที่ด้านบนควรแสดงให้เห็นว่าทำไมถ้าคุณแปลงการแทนค่าเป็นบิตและดูบิตต่ำคุณจะได้รับศูนย์ 75% ของเวลา นี่คือสาเหตุที่ค่าทั้งหมดน้อยกว่า 0.5 (ไบนารี0.1000) ซึ่งเป็นครึ่งหนึ่งของค่าที่เป็นไปได้ที่มีตั๊กแตนตำข้าวของพวกเขาเปลี่ยนไปทำให้ 0 ปรากฏในบิตต่ำ โดยทั่วไปสถานการณ์จะเหมือนกันเมื่อ mantissa มี 52 บิต (ไม่รวมถึงนัยที่ 1) เช่นเดียวกับที่doubleทำ

(อันที่จริงตามที่ @sneftel แนะนำในความคิดเห็นเราสามารถรวมค่าที่เป็นไปได้มากกว่า 16 ค่าในการแจกแจงโดยการสร้าง:

0.0001000 with probability 1/128
0.0001001 with probability 1/128
...
0.0001111 with probability 1/128
0.001000  with probability 1/64
0.001001  with probability 1/64
...
0.01111   with probability 1/32 
0.1000    with probability 1/16
0.1001    with probability 1/16
...
0.1110    with probability 1/16
0.1111    with probability 1/16

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


5
การใช้ทศนิยมเพื่อรับบิต / ไบต์แบบสุ่ม / อะไรก็ตามที่ทำให้ฉันตัวสั่นอยู่แล้ว แม้สำหรับการแจกแจงแบบสุ่มระหว่าง 0 ถึง n เรามีทางเลือกที่ดีกว่า (ดูที่ arc4random_uniform)กว่าการสุ่ม * n …
mirabilos
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.