เลขคู่ระหว่าง 0.0 ถึง 1.0 มีกี่ตัว?


95

นี่เป็นสิ่งที่อยู่ในความคิดของฉันมาหลายปีแล้ว แต่ฉันไม่เคยใช้เวลาถามมาก่อน

เครื่องกำเนิดตัวเลขสุ่ม (หลอก) จำนวนมากสร้างตัวเลขสุ่มระหว่าง 0.0 ถึง 1.0 ในทางคณิตศาสตร์มีจำนวนอนันต์ในช่วงนี้ แต่doubleเป็นจำนวนจุดลอยตัวดังนั้นจึงมีความแม่นยำ จำกัด

ดังนั้นคำถามคือ:

  1. มีกี่doubleตัวเลขระหว่าง 0.0 ถึง 1.0?
  2. มีจำนวนระหว่าง 1 ถึง 2 หรือไม่? ระหว่าง 100 ถึง 101? ระหว่าง 10 ^ 100 ถึง 10 ^ 100 + 1?

หมายเหตุ: ถ้ามันสร้างความแตกต่างฉันสนใจคำจำกัดความของ Java เป็นdoubleพิเศษ

คำตอบ:


68

Java doubleอยู่ในรูปแบบIEEE-754ดังนั้นจึงมีเศษส่วน 52 บิต ระหว่างสองพลังที่อยู่ติดกันของสองพลัง (รวมหนึ่งและไม่รวมของพลังถัดไป) ดังนั้นจึงมีกำลัง 2 ถึง 52 ที่แตกต่างกันdouble(เช่น 4503599627370496 ของพวกมัน) ตัวอย่างเช่นนั่นคือจำนวนdoubles ที่แตกต่างกันระหว่าง 0.5 ที่รวมและ 1.0 ที่ไม่รวมและจำนวนที่ไม่รวมอยู่ระหว่าง 1.0 รวมกับ 2.0 ยกเว้น

การนับdoublesระหว่าง 0.0 ถึง 1.0 นั้นยากกว่าการทำเช่นนั้นระหว่างพลังของสองเพราะมีพลังมากมายของสองรวมอยู่ในช่วงนั้นและอีกอย่างหนึ่งก็เข้าสู่ปัญหาที่มีหนามของตัวเลขที่ถูกทำให้เป็นมาตรฐาน 10 จาก 11 บิตของเลขชี้กำลังครอบคลุมช่วงที่เป็นปัญหาดังนั้นรวมถึงจำนวนที่ถูกทำให้เป็นมาตรฐาน (และฉันคิดว่ามีไม่กี่ชนิดNaN) คุณจะมี 1024 เท่าdoubleของการวางระหว่างกำลังของสอง - ไม่เกิน2**62ทั้งหมดอยู่ดี . ไม่รวม denormalized & C ผมเชื่อว่าการนับจะเป็น 1,023 2**52ครั้ง

สำหรับช่วงที่กำหนดเองเช่น "100 ถึง 100.1" จะยิ่งยากกว่าเพราะขอบเขตบนไม่สามารถแทนค่าได้อย่างแน่นอนdouble(ไม่ใช่ผลคูณที่แน่นอนของกำลังสองใด ๆ ) เพื่อเป็นการประมาณที่สะดวกเนื่องจากความก้าวหน้าระหว่างพลังของทั้งสองเป็นเชิงเส้นคุณสามารถพูดได้ว่าช่วงดังกล่าวเป็น0.1 / 64ช่วงระหว่างกำลังรอบสอง (64 และ 128) ดังนั้นคุณจึงคาดหวังว่า

(0.1 / 64) * 2**52

doubles ที่แตกต่าง- ซึ่งมาถึง7036874417766.4004... ให้หรือรับหนึ่งหรือสอง ;-)


@ อเล็กซ์: เพื่อทราบเมื่อฉันเขียน 100 ถึง 100.1 ฉันเขียนผิด ฉันหมายถึง 100 ถึง 101 โดยทั่วไประหว่าง N และ N + 1 สำหรับ N โดยพลการ
polygenelubricants

4
@ Alex: เพื่อให้ฉันได้รับตรงนี้สามารถมีได้ไม่เกิน2**64ค่าที่เป็นไปได้สองครั้ง (ตั้งแต่มันเป็นชนิด 64 บิต) และเห็นได้ชัดว่าเป็นสัดส่วนใหญ่ของค่าเหล่านั้นอยู่ระหว่าง0..1?
polygenelubricants

9
@polygene ใช่และใช่โดยเฉพาะประมาณหนึ่งในสี่ของค่าที่เป็นไปได้ (สำหรับการแสดงจุดลอยตัว "ปกติ" ของฐานใด ๆ และเลขชี้กำลังเทียบกับความยาวเศษส่วน) อยู่ระหว่าง 0.0 ถึง 1.0 (อีกหนึ่งในสี่ระหว่าง 1.0 ถึงอินฟินิตี้และ เหลืออีกครึ่งหนึ่งของครึ่งลบของแกนจริง) โดยพื้นฐานแล้วค่าครึ่งหนึ่งของเลขชี้กำลัง (โดยมีอคติปกติอยู่ครึ่งหนึ่งภายในช่วง) แสดงถึงพลังเชิงลบของฐานดังนั้นจึงเป็นตัวเลข <1.0
Alex Martelli

8
@polygenelubricants: สำหรับการใช้งานจำนวนมากช่วงระหว่าง 0 ถึง 1 นั้นมีความสำคัญและน่าสนใจมากกว่าช่วงระหว่าง 100 ถึง 101 มากนั่นเป็นสาเหตุที่ทำให้ได้รับส่วนแบ่งค่าที่มากขึ้น ตัวอย่างเช่นในทางฟิสิกส์คุณมักจะต้องจัดการกับค่าที่เล็กน้อยอย่างน่าขันเช่นค่าคงที่ความโน้มถ่วงของนิวตันที่ 6.67e-11 การมีความแม่นยำที่ดีมีประโยชน์มากกว่าระหว่าง 100 ถึง 101 อ่านfloating-point-gui.deสำหรับข้อมูลเพิ่มเติม
Michael Borgwardt

1
นอกจากนี้คุณยังสามารถปรับขนาดตัวเลขใด ๆ ให้อยู่ระหว่าง 0.0 ถึง 1.0 โดยติดตามมาตราส่วนแยกกันทำให้เกิดข้อผิดพลาดในการคำนวณน้อยลง เป็นเรื่องดีเมื่อสามารถจับคู่เส้นจำนวนทั้งหมดระหว่างสองหมายเลขได้!
codekaizen

44

ทุกdoubleค่าที่แสดงอยู่ระหว่าง0x0000000000000000และ0x3ff0000000000000อยู่ในช่วงเวลา [0.0, 1.0] นั่นคือ (2 ^ 62 - 2 ^ 52) ค่าที่แตกต่างกัน (บวกหรือลบคู่ขึ้นอยู่กับว่าคุณนับจุดสิ้นสุด)

ช่วง [1.0, 2.0] สอดคล้องกับการแสดงระหว่าง0x3ff0000000000000และ0x400000000000000; นั่นคือ 2 ^ 52 ค่าที่แตกต่างกัน

ช่วง [100.0, 101.0] สอดคล้องกับการแสดงระหว่าง0x4059000000000000และ0x4059400000000000; นั่นคือ 2 ^ 46 ค่าที่แตกต่างกัน

มีคู่ระหว่าง 10 ^ 100 และ 10 ^ 100 + 1 ไม่มีตัวเลขใดตัวเลขหนึ่งที่แสดงด้วยความเที่ยงตรงสองเท่าและไม่มีคู่ใดที่อยู่ระหว่างตัวเลขเหล่านั้น ตัวเลขความแม่นยำสองเท่าที่ใกล้เคียงที่สุด ได้แก่ :

99999999999999982163600188718701095...

และ

10000000000000000159028911097599180...

+1 สำหรับคำตอบที่แน่นอนที่ได้รับการสนับสนุนอย่างดี (หากคุณจู้จี้จุกจิกเกี่ยวกับการนับจุดสิ้นสุดโปรดจำไว้ว่า +0.0 และ -0.0 มีการนำเสนอที่แตกต่างกัน)
Jim Lewis

1
+1 ตอนจบที่พลิกผัน! รู้สึกเหมือนกำลังอ่านบท M. Night Shyamalan!
polygenelubricants

7

คนอื่น ๆ ได้อธิบายแล้วว่ามีคู่ผสมประมาณ 2 ^ 62 ในช่วง [0.0, 1.0]
(ไม่น่าแปลกใจจริงๆ: มีเกือบ 2 ^ 64 คู่ผสม จำกัด ที่แตกต่างกัน; ของบรรดาครึ่งหนึ่งเป็นบวกและประมาณครึ่งหนึ่งของผู้ที่มี <1.0.)

แต่คุณพูดถึงเครื่องกำเนิดตัวเลขสุ่มโปรดทราบว่าเครื่องกำเนิดตัวเลขสุ่มที่สร้างตัวเลขระหว่าง 0.0 ถึง 1.0 โดยทั่วไปไม่สามารถสร้างตัวเลขเหล่านี้ได้ทั้งหมด โดยทั่วไปจะสร้างเฉพาะตัวเลขของรูปแบบ n / 2 ^ 53 ที่มี n จำนวนเต็ม (ดูเช่นเอกสาร Java สำหรับnextDouble ) ดังนั้นโดยปกติจะมีค่าประมาณ 2 ^ 53 (+/- 1 ขึ้นอยู่กับจุดสิ้นสุดที่รวมอยู่) ค่าที่เป็นไปได้สำหรับrandom()เอาต์พุต ซึ่งหมายความว่าคู่ผสมส่วนใหญ่ใน [0.0, 1.0] จะไม่ถูกสร้างขึ้น


3

บทความคณิตศาสตร์ใหม่ของ Java ตอนที่ 2: ตัวเลขทศนิยมจาก IBM เสนอข้อมูลโค้ดต่อไปนี้เพื่อแก้ปัญหานี้ (ในการลอยตัว แต่ฉันสงสัยว่ามันใช้ได้กับคู่เช่นกัน):

public class FloatCounter {

    public static void main(String[] args) {
        float x = 1.0F;
        int numFloats = 0;
        while (x <= 2.0) {
            numFloats++;
            System.out.println(x);
            x = Math.nextUp(x);
        }
        System.out.println(numFloats);
    }
}

พวกเขามีความคิดเห็นเกี่ยวกับเรื่องนี้:

ปรากฎว่ามีการลอยตัว 8,388,609 ครั้งระหว่าง 1.0 ถึง 2.0 มีขนาดใหญ่ แต่แทบจะไม่ถึงอนันต์ที่นับไม่ได้ของจำนวนจริงที่มีอยู่ในช่วงนี้ ตัวเลขต่อเนื่องห่างกันประมาณ 0.0000001 ระยะนี้เรียกว่า ULP สำหรับหน่วยที่มีความแม่นยำน้อยที่สุดหรือหน่วยในตำแหน่งสุดท้าย


อ๋อ แต่ที่สำหรับfloat, ไม่ได้ double - floats มีมูลค่า 23 บิตของส่วนเพื่อให้2**23 -> 8388608ค่าที่แตกต่างกันระหว่างอำนาจที่อยู่ติดกันของทั้งสอง (ส่วน 'รวม' แน่นอนหมายความว่าคุณต้องนับหนึ่งเพิ่มเติมอำนาจต่อไปของสอง) doubles มีเศษส่วน 52 บิต!
Alex Martelli

1
@ อเล็กซ์: ฉันเดาว่าฉันจะต้องออกจากโปรแกรม (แก้ไขเป็นสองเท่า) วิ่งไปจนจบจักรวาลก่อนที่ฉันจะได้ผลลัพธ์ ... :(
Mark Rushakoff

1
ฉันรู้สึกโง่ ฉันเพิ่งเขียนสิ่งที่doubleเทียบเท่าและคิดว่า "เฮ้ฉันจะตอบคำถามของตัวเองในอีกประมาณ 5 นาที ... "
polygenelubricants

1
@polygene: นี่ให้ความรู้สึกเหมือนปัญหาของ Project Euler ที่วิธีการที่ชัดเจนนั้นไม่สามารถคำนวณได้ แต่ต้องมีสูตรง่ายๆที่ยอดเยี่ยมในการแก้ปัญหาโดยพลการ ...
Mark Rushakoff

2
อาจจะไม่ได้อยู่กับซูเปอร์ซูเปอร์อย่างแท้จริงบนเครื่องแค่เสี้ยววินาทีเพื่อให้ทำงานภายในวงนับด้วยdoubleระหว่างอำนาจที่อยู่ติดกันของทั้งสองจะใช้เวลาประมาณ 52 วัน (คนprintlnแน่นอนจะมากไม่น่าจะทำงานได้อย่างรวดเร็วไม่ว่าสิ่งที่ดังนั้น สมมติว่าข้อความหนึ่งหายไป ;-) ฉันคิดว่ามันเป็นไปได้ที่จะใช้เวลาหนึ่งปีหรือน้อยกว่ากับเครื่องจักรที่ทรงพลัง แต่สมจริง ;-)
Alex Martelli

2
  1. 2 ^ 53 - ขนาดของนัยสำคัญ / แมนทิสซาของเลขทศนิยม 64 บิตรวมถึงบิตที่ซ่อนอยู่
  2. ใช่แล้วเนื่องจาก sifnificand ได้รับการแก้ไข แต่เลขชี้กำลังเปลี่ยนไป

ดูบทความวิกิพีเดียสำหรับข้อมูลเพิ่มเติม


คำตอบของคุณสำหรับ 2 ข้อขัดแย้งกับวิธีที่ฉันเข้าใจการทำงานของ FP
polygenelubricants

ผมคิดว่า1เป็นสิ่งที่ผิดเพราะบิตซ่อนอยู่เสมอหนึ่ง - ดังนั้น2^52, ไม่ 2^53 แตกต่างกันค่า (ระหว่างอำนาจอยู่ติดกันของสองคนคนหนึ่งรวมและเป็นหนึ่งต่อไปยกเว้น - ไม่ได้ระหว่าง 0.0 และ 1.0)
Alex Martelli

1

Java double คือหมายเลข IEEE 754 binary64

ซึ่งหมายความว่าเราต้องพิจารณา:

  1. Mantissa คือ 52 บิต
  2. เลขชี้กำลังคือเลข 11 บิตที่มีอคติ 1023 (เช่นเพิ่ม 1023 เข้าไป)
  3. ถ้าเลขชี้กำลังเป็น 0 ทั้งหมดและแมนทิสซาไม่ใช่ศูนย์แสดงว่าตัวเลขนั้นไม่ได้ทำให้เป็นมาตรฐาน

โดยพื้นฐานแล้วหมายความว่ามีจำนวน 2 ^ 62-2 ^ 52 + 1 ของการแสดงสองครั้งที่เป็นไปได้ซึ่งตามมาตรฐานอยู่ระหว่าง 0 ถึง 1 โปรดทราบว่า 2 ^ 52 + 1 คือการลบกรณีที่ไม่เป็นมาตรฐาน ตัวเลข

จำไว้ว่าถ้าแมนทิสซาเป็นบวก แต่เลขชี้กำลังเป็นจำนวนลบจะเป็นบวก แต่น้อยกว่า 1 :-)

สำหรับตัวเลขอื่นจะยากกว่าเล็กน้อยเนื่องจากตัวเลขจำนวนเต็มขอบอาจไม่สามารถแสดงได้อย่างแม่นยำในการแทนค่า IEEE 754 และเนื่องจากมีบิตอื่น ๆ ที่ใช้ในเลขชี้กำลังเพื่อให้สามารถแทนตัวเลขได้ดังนั้นตัวเลขที่ยิ่งใหญ่ก็จะยิ่งต่ำลง ค่าต่างๆ

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