ตัวระบุความกว้าง Printf เพื่อรักษาความแม่นยำของค่าทศนิยม


103

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

ตัวอย่างเช่นสมมติว่าฉันพิมพ์ a floatด้วยความแม่นยำของ2ตำแหน่งทศนิยม:

float foobar = 0.9375;
printf("%.2f", foobar);    // prints out 0.94

เมื่อฉันสแกนผลลัพธ์0.94ฉันไม่มีการรับประกันที่เป็นไปตามมาตรฐานว่าฉันจะได้ค่า0.9375ทศนิยมดั้งเดิมกลับคืนมา (ในตัวอย่างนี้ฉันอาจจะไม่)

ฉันต้องการวิธีบอกprintfให้พิมพ์ค่าทศนิยมไปยังจำนวนหลักที่จำเป็นโดยอัตโนมัติเพื่อให้แน่ใจว่าสามารถสแกนกลับไปเป็นค่าเดิมที่ส่งไปให้printfได้

ฉันสามารถใช้มาโครบางตัวfloat.hเพื่อหาค่าความกว้างสูงสุดที่จะส่งผ่านไปprintfได้ แต่มีตัวระบุให้พิมพ์โดยอัตโนมัติตามจำนวนหลักที่มีนัยสำคัญหรือไม่หรืออย่างน้อยก็เป็นความกว้างสูงสุด


4
@bobobobo คุณแค่แนะนำให้คนใช้สมมติฐานออกไปในอากาศแทนที่จะใช้วิธีพกพา?

1
@ H2CO3 ไม่ฉันไม่แนะนำให้ใช้ "สมมติฐานออกจากอากาศ" ฉันขอแนะนำให้ใช้printf( "%f", val );ซึ่งพกพาได้มีประสิทธิภาพและเป็นค่าเริ่มต้นอยู่แล้ว
bobobobo

2
@bobobobo เพื่อที่ฉันจะได้เพิ่มเข้าไปในคำตอบคุณจะสามารถอ้างอิงอนุประโยคในมาตรฐาน C99 ซึ่งระบุว่าคำสั่ง printf จะส่งออกประเภท float ที่ความแม่นยำสูงสุดตามค่าเริ่มต้นหากไม่มีการระบุความแม่นยำ
Vilhelm Grey

1
@VilhelmGray ดีเป็น @chux doubleได้รับในมีบางคณิตศาสตร์ซับซ้อนสวยเป็นเพื่อความแม่นยำที่แท้จริงสำหรับคุณโดยเฉพาะ เนื่องจากคุณdoubleมีขนาดใหญ่มาก (ห่างจาก 1.0 มาก) จริง ๆ แล้วจะมีความแม่นยำน้อยกว่าในส่วนทศนิยม (ส่วนค่าน้อยกว่า 1.0) ดังนั้นคุณจึงไม่สามารถมีคำตอบที่น่าพอใจได้ที่นี่เพราะคำถามของคุณมีสมมติฐานผิด ๆ อยู่ในนั้น (กล่าวคือfloats / doubles ทั้งหมดถูกสร้างขึ้นเท่ากัน)
bobobobo

2
@Vilhelm Grey C11dr 5.2.4.2.2 "... จำนวนหลักทศนิยม n ดังนั้นจำนวนทศนิยมใด ๆ ที่มี p radix b หลักสามารถปัดเศษเป็นเลขทศนิยมที่มีทศนิยม n หลักแล้วย้อนกลับได้อีกครั้งโดยไม่มีการเปลี่ยนแปลง สำหรับค่า p log10 bb คือกำลัง 10 ⎡1 + p log10 b⎤มิฉะนั้น FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ... "6,10,10 คือค่าต่ำสุด
chux - คืนสถานะ Monica

คำตอบ:


92

ฉันแนะนำ @Jens Gustedt วิธีแก้ปัญหาเลขฐานสิบหก: ใช้% a

OP ต้องการ "พิมพ์ด้วยความแม่นยำสูงสุด (หรืออย่างน้อยก็ถึงทศนิยมที่สำคัญที่สุด)"

ตัวอย่างง่ายๆคือการพิมพ์หนึ่งในเจ็ดใน:

#include <float.h>
int Digs = DECIMAL_DIG;
double OneSeventh = 1.0/7.0;
printf("%.*e\n", Digs, OneSeventh);
// 1.428571428571428492127e-01

แต่มาเจาะลึก ...

ในทางคณิตศาสตร์คำตอบคือ "0.142857 142857 142857 ... " แต่เราใช้ตัวเลขทศนิยมที่มีความแม่นยำ จำกัด สมมติIEEE 754 แม่นยำสองไบนารี ดังนั้นOneSeventh = 1.0/7.0ผลลัพธ์ในค่าด้านล่าง นอกจากนี้ยังแสดงเป็นตัวเลขdoubleทศนิยมที่แสดงก่อนหน้าและต่อไปนี้

OneSeventh before = 0.1428571428571428 214571170656199683435261249542236328125
OneSeventh        = 0.1428571428571428 49212692681248881854116916656494140625
OneSeventh after  = 0.1428571428571428 769682682968777953647077083587646484375

การพิมพ์การแทนค่าทศนิยมที่แน่นอนdoubleมีการใช้งานที่ จำกัด

C มีมาโคร 2 ตระกูล<float.h>เพื่อช่วยเรา
ชุดแรกคือจำนวนหลักสำคัญที่จะพิมพ์เป็นสตริงเป็นทศนิยมดังนั้นเมื่อสแกนสตริงกลับเราจะได้จุดลอยตัวเดิม มีการแสดงด้วยค่าต่ำสุดของข้อมูลจำเพาะ C และตัวอย่างคอมไพเลอร์ C11

FLT_DECIMAL_DIG   6,  9 (float)                           (C11)
DBL_DECIMAL_DIG  10, 17 (double)                          (C11)
LDBL_DECIMAL_DIG 10, 21 (long double)                     (C11)
DECIMAL_DIG      10, 21 (widest supported floating type)  (C99)

ชุดที่สองคือจำนวนหลักสำคัญที่สตริงอาจถูกสแกนลงในทศนิยมแล้วพิมพ์ FP โดยยังคงนำเสนอสตริงเดิม มีการแสดงด้วยค่าต่ำสุดของข้อมูลจำเพาะ C และตัวอย่างคอมไพเลอร์ C11 ฉันเชื่อว่ามีจำหน่ายก่อน C99

FLT_DIG   6, 6 (float)
DBL_DIG  10, 15 (double)
LDBL_DIG 10, 18 (long double)

มาโครชุดแรกดูเหมือนจะตรงตามเป้าหมายของ OP ในเรื่องเลขนัยสำคัญ แต่มาโครนั้นไม่สามารถใช้ได้เสมอไป

#ifdef DBL_DECIMAL_DIG
  #define OP_DBL_Digs (DBL_DECIMAL_DIG)
#else  
  #ifdef DECIMAL_DIG
    #define OP_DBL_Digs (DECIMAL_DIG)
  #else  
    #define OP_DBL_Digs (DBL_DIG + 3)
  #endif
#endif

"+ 3" คือหัวใจสำคัญของคำตอบก่อนหน้านี้ของฉัน โดยเน้นที่การทราบสตริงการแปลงแบบไปกลับ - FP-string (ชุด # 2 มาโครที่มี C89) จะกำหนดตัวเลขสำหรับ FP-string-FP ได้อย่างไร (ตั้งค่ามาโคร # 1 ที่มีให้โพสต์ C89) โดยทั่วไปแล้วการบวก 3 คือผลลัพธ์

ตอนนี้วิธีการหลายอย่างมีนัยสำคัญ<float.h>หลักในการพิมพ์เป็นที่รู้จักกันและขับรถผ่าน

ในการพิมพ์เลขทศนิยมที่มีนัยสำคัญ N หลักหนึ่งอาจใช้รูปแบบต่างๆ

ด้วยความ"%e"ที่มีความแม่นยำฟิลด์คือจำนวนตัวเลขหลังหลักนำและจุดทศนิยม ดังนั้น- 1ในการสั่งซื้อ หมายเหตุ: นี่-1ไม่ได้อยู่ในการเริ่มต้นint Digs = DECIMAL_DIG;

printf("%.*e\n", OP_DBL_Digs - 1, OneSeventh);
// 1.4285714285714285e-01

ด้วยความ"%f"ที่มีความแม่นยำฟิลด์คือจำนวนตัวเลขหลังจุดทศนิยม สำหรับตัวเลขเช่นOneSeventh/1000000.0เราจะต้องOP_DBL_Digs + 6เห็นเลขนัยสำคัญทั้งหมด

printf("%.*f\n", OP_DBL_Digs    , OneSeventh);
// 0.14285714285714285
printf("%.*f\n", OP_DBL_Digs + 6, OneSeventh/1000000.0);
// 0.00000014285714285714285

หมายเหตุ: "%f"จำนวนมากที่มีการใช้งานเพื่อ ที่แสดง 6 หลักหลังจุดทศนิยม; 6 เป็นค่าเริ่มต้นของการแสดงผลไม่ใช่ความแม่นยำของตัวเลข


ทำไม 1.428571428571428492127e-01 ไม่ใช่ 1.428571428571428492127e-0 0 1 จำนวนหลักหลัง 'e' ควรเป็น 3
user1024

12.12.5 Floating-Point Conversionsบอกว่าค่าความแม่นยำเริ่มต้น%fคือ 6
Jingguo Yao

1
@ Jingguo Yao ยอมรับว่าการอ้างอิงระบุว่า "ความแม่นยำระบุจำนวนตัวเลขที่อยู่ตามอักขระจุดทศนิยมสำหรับ"% f "" คำว่า "ความแม่นยำ" ไม่ได้ใช้ในความหมายทางคณิตศาสตร์ แต่ใช้เพื่อกำหนดจำนวนหลักหลังจุดทศนิยม 1234567890.123 ในทางคณิตศาสตร์มีความแม่นยำหรือเลขนัยสำคัญ 13 หลัก 0.000000000123 มี 3 หลักของความแม่นยำทางคณิตศาสตร์ไม่ได้ 13. ลอยหมายเลขจุดมีคำตอบลอการิทึม distributed.This ใช้อย่างมีนัยสำคัญตัวเลขและความหมายทางคณิตศาสตร์ของความแม่นยำ
chux - คืนสถานะ Monica

1
@Slipp D. Thompson "มีการแสดงค่าต่ำสุดของข้อมูลจำเพาะ C และคอมไพเลอร์ C11 ตัวอย่าง "
chux - คืนสถานะ Monica

1
แน่นอนคุณถูกต้อง - เคล็ดลับของฉันใช้ได้เฉพาะกับค่าที่มีขนาดระหว่าง 1.0 ถึง 1.0eDBL_DIG ซึ่งเป็นช่วงเดียวที่เหมาะสำหรับการพิมพ์ด้วย"%f"ในตอนแรก แน่นอน"%e"ว่าการใช้ตามที่คุณแสดงให้เห็นนั้นเป็นแนวทางที่ดีกว่าในทุกๆด้านและเป็นคำตอบที่ดีอย่างมีประสิทธิภาพ (แม้ว่าอาจจะไม่ดีเท่าการใช้งาน"%a"ก็เป็นได้หากมีให้ใช้งานและแน่นอนว่า"%a"ควรใช้งานได้หากเป็น `` DBL_DECIMAL_DIG) ฉันมักจะต้องการตัวระบุรูปแบบซึ่งจะปัดเศษให้มีความแม่นยำสูงสุดเสมอ (แทนที่จะเป็นทศนิยม 6 ตำแหน่ง)
Greg A. Woods

66

คำตอบสั้น ๆ สำหรับการพิมพ์ตัวเลขทศนิยมโดยไม่สูญเสีย (ซึ่งสามารถอ่านกลับไปเป็นตัวเลขเดียวกันได้ทุกประการยกเว้น NaN และ Infinity):

  • ถ้าชนิดของคุณจะลอย: printf("%.9g", number)การใช้งาน
  • ถ้าชนิดของคุณเป็นสองเท่า: printf("%.17g", number)การใช้งาน

ห้ามใช้%fเนื่องจากจะระบุจำนวนตัวเลขที่มีนัยสำคัญหลังทศนิยมเท่านั้นและจะตัดทอนตัวเลขขนาดเล็ก สำหรับการอ้างอิงหมายเลขมายากล 9 และ 17 สามารถพบได้ในfloat.hที่กำหนดและFLT_DECIMAL_DIGDBL_DECIMAL_DIG


6
คุณสามารถอธิบายตัว%gระบุได้หรือไม่?
Vilhelm Grey

14
% g พิมพ์ตัวเลขที่มีตัวเลขมากเท่าที่จำเป็นเพื่อความแม่นยำโดยเลือกใช้ไวยากรณ์เลขชี้กำลังเมื่อตัวเลขมีขนาดเล็กหรือมาก (1e-5 แทนที่จะเป็น. 00005) และข้ามศูนย์ต่อท้าย (1 แทนที่จะเป็น 1.00000)
ccxvii

4
@truthseeker ในการแสดงรหัสไบนารี 64 ของ IEEE 754จำเป็นต้องพิมพ์ทศนิยมที่มีนัยสำคัญอย่างน้อย 15 ตำแหน่ง แต่ไม่คลุมเครือต้องการ 17 เนื่องจากความแม่นยำเปลี่ยนไปของเลขฐานสอง (ที่ 2,4,8 เป็นต้น) และเลขฐานสิบ (ที่ 10,100,1000 เป็นต้น) จะไม่อยู่ในตัวเลขเดียวกัน (ยกเว้น 1.0) ตัวอย่าง: 2 doubleค่าเหนือ0.1: 1.000_0000_0000_0000_2e-01, 1.000_0000_0000_0000_3e-01จำเป็นที่จะต้อง 17 หลักที่จะแยกแยะ
chux - คืนสถานะ Monica

3
@chux - คุณเข้าใจผิดเกี่ยวกับพฤติกรรมของ% .16g; มันไม่เพียงพอสำหรับตัวอย่างของคุณในการแยกแยะ 1.000_0000_0000_0000_2e-01 จาก 1.000_0000_0000_0000_3e-01 % .17g เป็นสิ่งจำเป็น
Don Hatch

1
@Don Hatch ฉันยอมรับ"%.16g"ไม่เพียงพอและ"%.17g"และ "%.16e"มีเพียงพอ รายละเอียดของ%gฉันจำผิด
chux - คืนสถานะ Monica

23

หากคุณสนใจเฉพาะบิต (รูปแบบฐานสิบหก) คุณสามารถใช้%aรูปแบบนี้ได้ สิ่งนี้รับประกันคุณ:

ความแม่นยำเริ่มต้นเพียงพอสำหรับการแสดงค่าที่แน่นอนหากการแทนค่าที่แน่นอนในฐาน 2 มีอยู่และมีขนาดใหญ่เพียงพอที่จะแยกแยะค่าของประเภท double

ฉันต้องเพิ่มว่าสิ่งนี้ใช้ได้ตั้งแต่ C99 เท่านั้น


16

ไม่มีไม่มีเช่นระบุความกว้าง printf พิมพ์ลอยจุดที่มีความแม่นยำสูงสุด ให้ฉันอธิบายว่าทำไม

ความแม่นยำสูงสุดfloatและdoubleเป็นตัวแปรและขึ้นอยู่กับค่าที่แท้จริงของหรือfloatdouble

เรียกคืนfloatและdoubleเก็บไว้ในรูปแบบsign.exponent.mantissa ซึ่งหมายความว่ามีบิตจำนวนมากที่ใช้สำหรับส่วนประกอบเศษส่วนสำหรับตัวเลขขนาดเล็กมากกว่าตัวเลขขนาดใหญ่

ใส่คำอธิบายภาพที่นี่

ตัวอย่างเช่นfloatสามารถแยกแยะระหว่าง 0.0 ถึง 0.1 ได้อย่างง่ายดาย

float r = 0;
printf( "%.6f\n", r ) ; // 0.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // 0.100000

แต่floatมีความคิดที่แตกต่างระหว่างไม่มีและ1e271e27 + 0.1

r = 1e27;
printf( "%.6f\n", r ) ; // 999999988484154753734934528.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // still 999999988484154753734934528.000000

นี่เป็นเพราะความแม่นยำทั้งหมด (ซึ่งถูก จำกัด ด้วยจำนวนบิตแมนทิสซา) ถูกใช้สำหรับส่วนใหญ่ของตัวเลขทางด้านซ้ายของทศนิยม

%.fปรับปรุงเพียงแค่บอกว่าวิธีการหลายค่าทศนิยมคุณต้องการที่จะพิมพ์จากจำนวนลอยเท่าที่จัดรูปแบบไป ความจริงที่ว่าความแม่นยำที่มีขึ้นอยู่กับขนาดของตัวเลขนั้นขึ้นอยู่กับคุณในฐานะโปรแกรมเมอร์ที่จะจัดการ printfไม่สามารถ / ไม่จัดการสิ่งนั้นให้คุณ


2
นี่เป็นคำอธิบายที่ดีเยี่ยมเกี่ยวกับข้อ จำกัด ของการพิมพ์ค่าทศนิยมให้เป็นทศนิยมเฉพาะอย่างแม่นยำ อย่างไรก็ตามฉันเชื่อว่าฉันคลุมเครือเกินไปกับตัวเลือกคำเดิมของฉันดังนั้นฉันจึงอัปเดตคำถามของฉันเพื่อหลีกเลี่ยงคำว่า "ความแม่นยำสูงสุด" โดยหวังว่าจะช่วยคลายความสับสนได้
Vilhelm Grey

ยังคงขึ้นอยู่กับค่าของตัวเลขที่คุณกำลังพิมพ์
bobobobo

3
นี่เป็นความจริงบางส่วน แต่ไม่ตอบคำถามและคุณสับสนว่า OP กำลังถามอะไร เขากำลังถามว่ามีใครสามารถค้นหาตัวเลข [ทศนิยม] หลักที่มีนัยสำคัญที่floatให้มาได้หรือไม่และคุณยืนยันว่าไม่มีสิ่งนั้น (กล่าวคือไม่มีFLT_DIG) ซึ่งผิด

@ H2CO3 บางทีคุณควรแก้ไขโพสต์และโหวตลง (j / k) คำตอบนี้ยืนยันว่าFLT_DIGไม่มีความหมายอะไร คำตอบนี้อ้างจำนวนตำแหน่งทศนิยมที่มีอยู่ขึ้นอยู่กับค่าภายในลอย
bobobobo

1
คุณสมมติว่าตัวอักษรรูปแบบต้องเป็น "f" หรือไม่? ฉันไม่คิดว่าจำเป็น การอ่านคำถามของฉันคือ OP กำลังมองหาตัวระบุรูปแบบ printf บางตัวที่สร้างการเดินทางไปกลับแบบไม่สูญเสียดังนั้นคำตอบของ @ccxvii ("% .9g" สำหรับ float, "% .17g" สำหรับ double) คือ a สิ่งที่ดี. คำถามอาจจะดีกว่าโดยเอาคำว่า "width" ออก
ดอนฟัก

11

เพียงใช้มาโครจาก<float.h>และตัวระบุการแปลงความกว้างตัวแปร ( ".*"):

float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);

2
@OliCharlesworth คุณหมายถึงเช่นนั้น: printf("%." FLT_DIG "f\n", f);
Vilhelm Gray

3
+1 แต่วิธีนี้ใช้ได้ดีที่สุด%eสำหรับ%f : 1.0แต่ถ้ามันคือรู้ว่าคุ้มค่าที่จะพิมพ์อยู่ใกล้กับ
Pascal Cuoq

3
%eพิมพ์เลขนัยสำคัญสำหรับตัวเลขที่มีขนาดเล็กมากและ%fไม่พิมพ์ เช่นx = 1e-100. %.5fภาพพิมพ์0.00000(การสูญเสียการหดตัวทั้งหมด) %.5eพิมพ์1.00000e-100พิมพ์
chux - คืนสถานะ Monica

1
@bobobobo นอกจากนี้คุณคิดผิดที่ "ให้เหตุผลที่ถูกต้องมากกว่า" FLT_DIGถูกกำหนดให้เป็นค่าที่กำหนดด้วยเหตุผล ถ้าเป็น 6 นั่นเป็นเพราะfloatไม่สามารถเก็บความแม่นยำได้มากกว่า 6 หลัก หากคุณพิมพ์โดยใช้%.7fตัวเลขสุดท้ายจะไม่มีความหมาย คิดก่อนที่จะลงคะแนน

5
@bobobobo ไม่%.6fไม่เทียบเท่าเพราะFLT_DIGไม่เสมอ 6. แล้วใครจะสนใจเรื่องประสิทธิภาพ? I / O มีราคาแพงอยู่แล้วความแม่นยำที่มากขึ้นหรือน้อยลงหนึ่งหลักจะไม่ทำให้คอขวด

5

ฉันทำการทดลองเล็ก ๆ เพื่อตรวจสอบว่าการพิมพ์ด้วยDBL_DECIMAL_DIGจะรักษาการแสดงเลขฐานสองของตัวเลขไว้อย่างแน่นอน ปรากฎว่าสำหรับคอมไพเลอร์และไลบรารี C ที่ฉันลองนั้นDBL_DECIMAL_DIGเป็นจำนวนหลักที่ต้องการและการพิมพ์ที่มีตัวเลขน้อยกว่าแม้แต่หลักเดียวก็สร้างปัญหาสำคัญ

#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

union {
    short s[4];
    double d;
} u;

void
test(int digits)
{
    int i, j;
    char buff[40];
    double d2;
    int n, num_equal, bin_equal;

    srand(17);
    n = num_equal = bin_equal = 0;
    for (i = 0; i < 1000000; i++) {
        for (j = 0; j < 4; j++)
            u.s[j] = (rand() << 8) ^ rand();
        if (isnan(u.d))
            continue;
        n++;
        sprintf(buff, "%.*g", digits, u.d);
        sscanf(buff, "%lg", &d2);
        if (u.d == d2)
            num_equal++;
        if (memcmp(&u.d, &d2, sizeof(double)) == 0)
            bin_equal++;
    }
    printf("Tested %d values with %d digits: %d found numericaly equal, %d found binary equal\n", n, digits, num_equal, bin_equal);
}

int
main()
{
    test(DBL_DECIMAL_DIG);
    test(DBL_DECIMAL_DIG - 1);
    return 0;
}

ฉันรันด้วยคอมไพเลอร์ C ของ Microsoft 19.00.24215.1 และ gcc เวอร์ชัน 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1) การใช้ตัวเลขทศนิยมที่น้อยกว่าหนึ่งตัวจะลดจำนวนตัวเลขที่เปรียบเทียบเท่ากับครึ่งหนึ่ง (ฉันยังตรวจสอบว่าrand()ตามที่ใช้จริงจะสร้างตัวเลขที่แตกต่างกันประมาณหนึ่งล้าน) นี่คือผลลัพธ์โดยละเอียด

ไมโครซอฟต์ค

ทดสอบค่า 999507 ด้วยตัวเลข 17 หลัก: 999507 พบว่าตัวเลขเท่ากับ 999507 พบว่าไบนารีเท่ากับ
ทดสอบค่า 999507 ด้วยตัวเลข 16 หลัก: 545389 พบว่าตัวเลขเท่ากับ 545389 พบว่าไบนารีเท่ากับ

GCC

ทดสอบค่า 999485 ด้วย 17 หลัก: 999485 พบว่าตัวเลขเท่ากับ 999485 พบว่าไบนารีเท่ากับ
ทดสอบค่า 999485 ด้วยตัวเลข 16 หลัก: 545402 พบว่าตัวเลขเท่ากับ 545402 พบไบนารีเท่ากับ

1
"ทำงานนี้กับไมโครซอฟท์คอมไพเลอร์ C" -> RAND_MAX == 32767คอมไพเลอร์ที่อาจมี พิจารณาu.s[j] = (rand() << 8) ^ rand();หรือชอบที่จะทำให้บิตทั้งหมดมีโอกาสเป็น 0 หรือ 1
chux - Reinstate Monica

อันที่จริง RAND_MAX คือ 32767 ดังนั้นข้อเสนอของคุณจึงถูกต้อง
Diomidis Spinellis

1
ฉันอัปเดตโพสต์เพื่อจัดการ RAND_MAX ตามที่ @ chux-ReinstateMonica แนะนำ ผลลัพธ์จะคล้ายกับที่ได้รับก่อนหน้านี้
Diomidis Spinellis

3

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

static unsigned int
ilog10(uintmax_t v);

/*
 * Note:  As presented this demo code prints a whole line including information
 * about how the form was arrived with, as well as in certain cases a couple of
 * interesting details about the number, such as the number of decimal places,
 * and possibley the magnitude of the value and the number of significant
 * digits.
 */
void
print_decimal(double d)
{
        size_t sigdig;
        int dplaces;
        double flintmax;

        /*
         * If we really want to see a plain decimal presentation with all of
         * the possible significant digits of precision for a floating point
         * number, then we must calculate the correct number of decimal places
         * to show with "%.*f" as follows.
         *
         * This is in lieu of always using either full on scientific notation
         * with "%e" (where the presentation is always in decimal format so we
         * can directly print the maximum number of significant digits
         * supported by the representation, taking into acount the one digit
         * represented by by the leading digit)
         *
         *        printf("%1.*e", DBL_DECIMAL_DIG - 1, d)
         *
         * or using the built-in human-friendly formatting with "%g" (where a
         * '*' parameter is used as the number of significant digits to print
         * and so we can just print exactly the maximum number supported by the
         * representation)
         *
         *         printf("%.*g", DBL_DECIMAL_DIG, d)
         *
         *
         * N.B.:  If we want the printed result to again survive a round-trip
         * conversion to binary and back, and to be rounded to a human-friendly
         * number, then we can only print DBL_DIG significant digits (instead
         * of the larger DBL_DECIMAL_DIG digits).
         *
         * Note:  "flintmax" here refers to the largest consecutive integer
         * that can be safely stored in a floating point variable without
         * losing precision.
         */
#ifdef PRINT_ROUND_TRIP_SAFE
# ifdef DBL_DIG
        sigdig = DBL_DIG;
# else
        sigdig = ilog10(uipow(FLT_RADIX, DBL_MANT_DIG - 1));
# endif
#else
# ifdef DBL_DECIMAL_DIG
        sigdig = DBL_DECIMAL_DIG;
# else
        sigdig = (size_t) lrint(ceil(DBL_MANT_DIG * log10((double) FLT_RADIX))) + 1;
# endif
#endif
        flintmax = pow((double) FLT_RADIX, (double) DBL_MANT_DIG); /* xxx use uipow() */
        if (d == 0.0) {
                printf("z = %.*s\n", (int) sigdig + 1, "0.000000000000000000000"); /* 21 */
        } else if (fabs(d) >= 0.1 &&
                   fabs(d) <= flintmax) {
                dplaces = (int) (sigdig - (size_t) lrint(ceil(log10(ceil(fabs(d))))));
                if (dplaces < 0) {
                        /* XXX this is likely never less than -1 */
                        /*
                         * XXX the last digit is not significant!!! XXX
                         *
                         * This should also be printed with sprintf() and edited...
                         */
                        printf("R = %.0f [%d too many significant digits!!!, zero decimal places]\n", d, abs(dplaces));
                } else if (dplaces == 0) {
                        /*
                         * The decimal fraction here is not significant and
                         * should always be zero  (XXX I've never seen this)
                         */
                        printf("R = %.0f [zero decimal places]\n", d);
                } else {
                        if (fabs(d) == 1.0) {
                                /*
                                 * This is a special case where the calculation
                                 * is off by one because log10(1.0) is 0, but
                                 * we still have the leading '1' whole digit to
                                 * count as a significant digit.
                                 */
#if 0
                                printf("ceil(1.0) = %f, log10(ceil(1.0)) = %f, ceil(log10(ceil(1.0))) = %f\n",
                                       ceil(fabs(d)), log10(ceil(fabs(d))), ceil(log10(ceil(fabs(d)))));
#endif
                                dplaces--;
                        }
                        /* this is really the "useful" range of %f */
                        printf("r = %.*f [%d decimal places]\n", dplaces, d, dplaces);
                }
        } else {
                if (fabs(d) < 1.0) {
                        int lz;

                        lz = abs((int) lrint(floor(log10(fabs(d)))));
                        /* i.e. add # of leading zeros to the precision */
                        dplaces = (int) sigdig - 1 + lz;
                        printf("f = %.*f [%d decimal places]\n", dplaces, d, dplaces);
                } else {                /* d > flintmax */
                        size_t n;
                        size_t i;
                        char *df;

                        /*
                         * hmmmm...  the easy way to suppress the "invalid",
                         * i.e. non-significant digits is to do a string
                         * replacement of all dgits after the first
                         * DBL_DECIMAL_DIG to convert them to zeros, and to
                         * round the least significant digit.
                         */
                        df = malloc((size_t) 1);
                        n = (size_t) snprintf(df, (size_t) 1, "%.1f", d);
                        n++;                /* for the NUL */
                        df = realloc(df, n);
                        (void) snprintf(df, n, "%.1f", d);
                        if ((n - 2) > sigdig) {
                                /*
                                 * XXX rounding the integer part here is "hard"
                                 * -- we would have to convert the digits up to
                                 * this point back into a binary format and
                                 * round that value appropriately in order to
                                 * do it correctly.
                                 */
                                if (df[sigdig] >= '5' && df[sigdig] <= '9') {
                                        if (df[sigdig - 1] == '9') {
                                                /*
                                                 * xxx fixing this is left as
                                                 * an exercise to the reader!
                                                 */
                                                printf("F = *** failed to round integer part at the least significant digit!!! ***\n");
                                                free(df);
                                                return;
                                        } else {
                                                df[sigdig - 1]++;
                                        }
                                }
                                for (i = sigdig; df[i] != '.'; i++) {
                                        df[i] = '0';
                                }
                        } else {
                                i = n - 1; /* less the NUL */
                                if (isnan(d) || isinf(d)) {
                                        sigdig = 0; /* "nan" or "inf" */
                                }
                        }
                        printf("F = %.*s. [0 decimal places, %lu digits, %lu digits significant]\n",
                               (int) i, df, (unsigned long int) i, (unsigned long int) sigdig);
                        free(df);
                }
        }

        return;
}


static unsigned int
msb(uintmax_t v)
{
        unsigned int mb = 0;

        while (v >>= 1) { /* unroll for more speed...  (see ilog2()) */
                mb++;
        }

        return mb;
}

static unsigned int
ilog10(uintmax_t v)
{
        unsigned int r;
        static unsigned long long int const PowersOf10[] =
                { 1LLU, 10LLU, 100LLU, 1000LLU, 10000LLU, 100000LLU, 1000000LLU,
                  10000000LLU, 100000000LLU, 1000000000LLU, 10000000000LLU,
                  100000000000LLU, 1000000000000LLU, 10000000000000LLU,
                  100000000000000LLU, 1000000000000000LLU, 10000000000000000LLU,
                  100000000000000000LLU, 1000000000000000000LLU,
                  10000000000000000000LLU };

        if (!v) {
                return ~0U;
        }
        /*
         * By the relationship "log10(v) = log2(v) / log2(10)", we need to
         * multiply "log2(v)" by "1 / log2(10)", which is approximately
         * 1233/4096, or (1233, followed by a right shift of 12).
         *
         * Finally, since the result is only an approximation that may be off
         * by one, the exact value is found by subtracting "v < PowersOf10[r]"
         * from the result.
         */
        r = ((msb(v) * 1233) >> 12) + 1;

        return r - (v < PowersOf10[r]);
}

ฉันไม่สนใจว่ามันจะตอบคำถามหรือไม่ - นี่เป็นสิ่งที่น่าประทับใจจริงๆ ต้องใช้ความคิดและควรรับทราบและชมเชย อาจจะเป็นการดีถ้าคุณจะรวมโค้ดแบบเต็ม (ไม่ว่าจะที่นี่หรือที่อื่น) เพื่อทดสอบ แต่ถึงแม้จะไม่มีก็เป็นงานที่ดีจริงๆ มี +1 สำหรับสิ่งนั้น!
Pryftan

0

ความรู้ของฉันมีขั้นตอนวิธีการกระจายอย่างดีช่วยให้การส่งออกไปยังหมายเลขที่จำเป็นของเลขนัยสำคัญดังกล่าวว่าเมื่อสแกนกลับสตริงในมูลค่าจุดเดิมลอยจะได้รับในการdtoa.cเขียนโดยแดเนียลเกย์ซึ่งมีอยู่ที่นี่ใน netlib (ดูกระดาษที่เกี่ยวข้องด้วย) รหัสนี้ใช้เช่นใน Python, MySQL, Scilab และอื่น ๆ อีกมากมาย

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