อะไรคือคู่ที่ใกล้เคียงที่สุดกับ 1.0 นั่นไม่ใช่ 1.0


89

มีวิธีทางโปรแกรมในการรับ double ที่ใกล้เคียงกับ 1.0 มากที่สุด แต่จริงๆแล้วไม่ใช่ 1.0 หรือไม่?

วิธีแฮ็ควิธีหนึ่งในการทำเช่นนี้คือการ memcpy สองเท่าให้เป็นจำนวนเต็มขนาดเท่ากันแล้วลบออก วิธีการทำงานของรูปแบบจุดลอยตัวของ IEEE754 สิ่งนี้จะจบลงด้วยการลดเลขชี้กำลังทีละหนึ่งในขณะที่เปลี่ยนส่วนเศษส่วนจากศูนย์ทั้งหมด (1.000000000000) เป็นค่าทั้งหมด (1.111111111111) อย่างไรก็ตามมีเครื่องจักรที่เก็บจำนวนเต็ม little-endian ในขณะที่ floating-point ถูกจัดเก็บ big-endian ดังนั้นจึงไม่สามารถใช้ได้เสมอไป


4
คุณไม่สามารถสันนิษฐานได้ว่า +1 คือระยะทางเท่ากัน (จาก 1.0) เป็น -1 การซ้อนกันของการแสดงจุดลอยตัวฐาน 10 และฐาน 2 หมายความว่าช่องว่างไม่สม่ำเสมอ
Richard Critten

2
@ ริชาร์ด: คุณพูดถูก ไม่น่าเป็นไปได้มากที่การลบหนึ่ง ULP จะได้ค่าเอ้อ "nextbefore" เพราะฉันเดาว่าเลขชี้กำลังจะต้องได้รับการปรับด้วยเช่นกัน nextafter()เป็นวิธีเดียวที่เหมาะสมในการบรรลุสิ่งที่เขาต้องการ
Rudy Velthuis

1
FYI ได้อ่านของบล็อกนี้ (ไม่ระเบิด): exploringbinary.com/...
ริชาร์ด Critten

1
@RudyVelthuis: ใช้งานได้กับทุกรูปแบบจุดลอยตัวไบนารี IEEE754
Edgar Bonet

1
โอเคบอกฉันหน่อยสิว่า "อะไรที่ใช้ได้กับทุกรูปแบบทศนิยม IEEE754" ไม่เป็นความจริงที่ว่าถ้าคุณลดค่านัยสำคัญและคุณจะได้ค่า "firstbefore ()" โดยเฉพาะอย่างยิ่งไม่ใช่สำหรับ 1.0 ซึ่งมีนัยสำคัญซึ่งเป็นกำลังสอง นั่นหมายความว่า1.0000...ไบนารีกำลังลดลง0.111111....และเพื่อทำให้เป็นปกติคุณต้องเลื่อนไปทางซ้าย: 1.11111...ซึ่งคุณต้องลดเลขชี้กำลัง แล้วคุณอยู่ห่างจาก 1.0 2 ulp ดังนั้นไม่การลบค่าหนึ่งออกจากค่าอินทิกรัลไม่ได้ให้สิ่งที่ถามที่นี่
Rudy Velthuis

คำตอบ:


23

ใน C และ C ++ ค่าต่อไปนี้ให้ค่าที่ใกล้เคียงที่สุดกับ 1.0:

#include <limits.h>

double closest_to_1 = 1.0 - DBL_EPSILON/FLT_RADIX;

อย่างไรก็ตามโปรดทราบว่าใน C ++ เวอร์ชันที่ใหม่กว่าlimits.hจะถูกเลิกใช้งานในรูปแบบclimits. แต่ถ้าคุณใช้รหัสเฉพาะ C ++ อยู่แล้วคุณสามารถใช้

#include <limits>

typedef std::numeric_limits<double> lim_dbl;
double closest_to_1 = 1.0 - lim_dbl::epsilon()/lim_dbl::radix;

และตามที่ Jarod42 เขียนในคำตอบของเขาตั้งแต่ C99 หรือ C ++ 11 คุณยังสามารถใช้nextafter:

#include <math.h>

double closest_to_1 = nextafter(1.0, 0.0);

แน่นอนใน C ++ คุณสามารถ (และสำหรับ C ++ รุ่นที่ใหม่กว่า) ควรรวมcmathและใช้std::nextafterแทน


144

ตั้งแต่ C ++ 11 คุณสามารถใช้nextafterเพื่อรับค่าที่แสดงได้ถัดไปในทิศทางที่กำหนด:

std::nextafter(1., 0.); // 0.99999999999999989
std::nextafter(1., 2.); // 1.0000000000000002

การสาธิต


11
std::ceil(std::nextafter(1., std::numeric_limits<double>::max()))นอกจากนี้ยังเป็นวิธีที่ดีเพื่อเพิ่มคู่เป็นจำนวนเต็มซึ่งแสดงต่อไปนี้:
Johannes Schaub - litb

44
คำถามต่อไปคือ "วิธีการใช้งานนี้ใน stdlib": P
Lightness Races in Orbit

18
หลังจากอ่านความคิดเห็นของ @ LightnessRacesinOrbit ฉันก็สงสัย นี่คือวิธีการดำเนินการ glibcnextafter , และนี่คือวิธีการดำเนินการคิดถึงมันในกรณีที่คนอื่นต้องการที่จะเห็นว่ามันทำ โดยทั่วไป: บิดบิตดิบ
Cornstalks

2
@Cornstalks: ฉันไม่แปลกใจเลยที่มันลดลงเล็กน้อยตัวเลือกอื่น ๆ เท่านั้นที่จะรองรับ CPU
Matthieu M.

5
การบิดเล็กน้อยเป็นวิธีเดียวที่จะทำได้อย่างถูกต้อง IMO คุณสามารถทำการทดสอบได้หลายครั้งโดยพยายามเข้าใกล้อย่างช้าๆ แต่อาจช้ามาก
Rudy Velthuis

23

ใน C คุณสามารถใช้สิ่งนี้:

#include <float.h>
...
double value = 1.0+DBL_EPSILON;

DBL_EPSILON คือความแตกต่างระหว่าง 1 และค่าต่ำสุดที่มากกว่า 1 ที่สามารถแสดงได้

คุณจะต้องพิมพ์เป็นตัวเลขหลายหลักจึงจะเห็นค่าที่แท้จริง

บนแพลตฟอร์มของฉันให้printf("%.16lf",1.0+DBL_EPSILON)1.0000000000000002


10
เพื่อให้ทำงานตามปกติสำหรับค่าอื่น ๆ บางกว่า1.เป็น1'000'000 Demo
Jarod42

7
@ Jarod42: คุณขวา แต่ OP 1.0ถามเฉพาะเกี่ยวกับ BTW ยังให้ค่าที่ใกล้เคียงที่สุดมากกว่า 1 และไม่ใช่ค่าที่ใกล้เคียงที่สุดกับ 1 (ซึ่งอาจน้อยกว่า 1) ดังนั้นฉันยอมรับว่านี่เป็นคำตอบบางส่วน แต่ฉันคิดว่ามันสามารถมีส่วนร่วมได้
barak manos

@ LưuVĩnhPhúc: ฉันให้ความแม่นยำเกี่ยวกับข้อ จำกัด ของคำตอบและใกล้เคียงที่สุดในทิศทางอื่น ๆ
จรด 42

7
สิ่งนี้ไม่ได้ให้คู่ที่ใกล้เคียงที่สุดกับ 1.0 เนื่องจาก (สมมติว่าฐาน 2) คู่ทางขวาก่อน 1.0 อยู่ห่างออกไปเพียงครึ่งเดียวของคู่หลัง 1.0 (ซึ่งเป็นค่าที่คุณคำนวณ)
celtschk

@celtschk: คุณพูดถูกฉันได้อธิบายไปแล้วในความคิดเห็นด้านบน
barak manos

4

ใน C ++ คุณสามารถใช้สิ่งนี้ได้

1 + std::numeric_limits<double>::epsilon()

1
เช่นเดียวกับคำตอบของ barak manos สิ่งนี้จะใช้ไม่ได้กับค่าอื่นใดนอกจาก 1.
zwol

2
@zwol tehnically สำหรับการใช้งานจุดลอยตัวไบนารีทั่วไปมันจะใช้ได้กับค่าใด ๆ ระหว่าง 1 ถึง 2-epsilon แต่ใช่คุณคิดถูกที่รับประกันว่าจะสมัครกับ 1 เท่านั้น
Random832

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