Thông số độ rộng printf để duy trì độ chính xác của giá trị dấu phẩy động


103

Có bộ printfxác định độ rộng nào có thể được áp dụng cho bộ xác định dấu phẩy động sẽ tự động định dạng đầu ra thành số chữ số có nghĩa cần thiết để khi quét lại chuỗi, giá trị dấu phẩy động ban đầu có được không?

Ví dụ: giả sử tôi in floata chính xác đến 2vị trí thập phân:

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

Khi tôi quét đầu ra 0.94, tôi không có đảm bảo tuân thủ tiêu chuẩn rằng tôi sẽ lấy 0.9375lại giá trị dấu phẩy động ban đầu (trong ví dụ này, tôi có thể sẽ không).

Tôi muốn có một cách printfđể tự động in giá trị dấu phẩy động đến số chữ số có nghĩa cần thiết để đảm bảo rằng nó có thể được quét trở lại giá trị ban đầu được chuyển đến printf.

Tôi có thể sử dụng một số macro trong float.hđể tính chiều rộng tối đa cần chuyển đến printf, nhưng đã có bộ định nghĩa để tự động in đến số lượng chữ số có nghĩa cần thiết - hoặc ít nhất là chiều rộng tối đa chưa?


4
@bobobobo Vì vậy, bạn chỉ đang khuyến nghị rằng một người sử dụng một giả định ngoài không khí thay vì sử dụng phương pháp di động?

1
@ H2CO3 Không, tôi không khuyên bạn nên sử dụng "giả định ngoài không khí", tôi khuyên bạn nên sử dụng printf( "%f", val );cái đã có sẵn, hiệu quả và mặc định.
bobobobo

2
@bobobobo Để tôi có thể thêm nó vào câu trả lời, bạn có thể trích dẫn mệnh đề trong tiêu chuẩn C99 quy định rằng câu lệnh printf sẽ xuất ra kiểu float ở độ chính xác tối đa theo mặc định nếu không có độ chính xác nào được chỉ định không?
Vilhelm Grey

1
@VilhelmGray Cũng như @chux đi vào, có một số phép toán khá phức tạp về độ chính xác thực tế cho cụ thể của bạn double. Khi của bạn doublecực kỳ lớn (rất xa so với 1,0), nó thực sự trở nên kém chính xác hơn ở phần thập phân (phần giá trị nhỏ hơn 1,0). Vì vậy, bạn thực sự không thể có câu trả lời thỏa đáng ở đây, bởi vì câu hỏi của bạn có một giả định sai trong đó (cụ thể là tất cả các floats / doubles được tạo ra như nhau)
bobobobo

2
@Vilhelm Grey C11dr 5.2.4.2.2 "... số chữ số thập phân, n, sao cho bất kỳ số dấu phẩy động nào có p cơ số b đều có thể được làm tròn thành số dấu phẩy động với n chữ số thập phân và quay lại mà không thay đổi với giá trị, p log10 bb là một sức mạnh của 10 ⎡1 + p log10 b⎤ khác FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ..." các 6,10,10 là tối thiểu giá trị.
Chux - Khôi phục Monica

Câu trả lời:


92

Tôi đề xuất @Jens Gustedt giải pháp thập lục phân: sử dụng% a.

OP muốn “in với độ chính xác tối đa (hoặc ít nhất đến số thập phân quan trọng nhất)”.

Một ví dụ đơn giản sẽ là in một phần bảy như trong:

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

Nhưng hãy tìm hiểu sâu hơn ...

Về mặt toán học, câu trả lời là "0,142857 142857 142857 ...", nhưng chúng tôi đang sử dụng số dấu phẩy động có độ chính xác hữu hạn. Hãy giả sử IEEE 754 nhị phân chính xác kép . Vì vậy, OneSeventh = 1.0/7.0kết quả trong giá trị dưới đây. Cũng được hiển thị là các doublesố dấu phẩy động có thể biểu diễn trước và sau .

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

Việc in biểu diễn thập phân chính xác của a doublebị hạn chế sử dụng.

C có 2 họ macro <float.h>để giúp chúng tôi.
Tập hợp đầu tiên là số chữ số có nghĩa cần in trong một chuỗi ở dạng thập phân nên khi quét lại chuỗi, chúng ta nhận được dấu phẩy động ban đầu. Được hiển thị với giá trị tối thiểu của thông số C và một trình biên dịch C11 mẫu .

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)

Bộ thứ hai là số chữ số có nghĩa mà một chuỗi có thể được quét thành dấu phẩy động và sau đó FP được in ra, vẫn giữ nguyên cách trình bày chuỗi đó. Được hiển thị với giá trị tối thiểu của thông số C và một trình biên dịch C11 mẫu . Tôi tin rằng có sẵn trước C99.

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

Tập hợp các macro đầu tiên dường như đáp ứng mục tiêu của OP là các chữ số có nghĩa . Nhưng macro đó không phải lúc nào cũng có sẵn.

#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" là mấu chốt của câu trả lời trước đây của tôi. Nó tập trung vào việc nếu biết chuỗi chuyển đổi khứ hồi-FP-string (bộ # 2 macro có sẵn C89), làm cách nào để xác định các chữ số cho FP-string-FP (bộ số 1 macro có sẵn bài C89)? Nói chung, thêm 3 là kết quả.

Bây giờ có bao nhiêu chữ số có nghĩa để in đã được biết và định hướng qua <float.h>.

Để in N chữ số thập phân có nghĩa, người ta có thể sử dụng nhiều định dạng khác nhau.

Với "%e", trường độ chính xác là số chữ số sau chữ số chính và dấu thập phân. Theo - 1thứ tự cũng vậy. Lưu ý: Đây -1không phải là phần đầuint Digs = DECIMAL_DIG;

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

Với "%f", trường độ chính xác là số chữ số sau dấu thập phân. Đối với một số như vậy OneSeventh/1000000.0, người ta sẽ cần OP_DBL_Digs + 6phải xem tất cả các chữ số có nghĩa .

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

Lưu ý: Nhiều người đang sử dụng "%f". Điều đó hiển thị 6 chữ số sau dấu thập phân; 6 là mặc định hiển thị, không phải là độ chính xác của số.


tại sao lại là 1.428571428571428492127e-01 mà không phải là 1.428571428571428492127e-0 0 1, số chữ số sau 'e' phải là 3?
user1024

12.12.5 Floating-Point Chuyển đổi nói chính xác mặc định cho %flà 6.
Jingguo Yao

1
@Jingguo Yao Đồng ý rằng tham chiếu cho biết "Độ chính xác chỉ định bao nhiêu chữ số theo sau ký tự dấu thập phân cho '% f'". Từ "độ chính xác" ở đó không được sử dụng theo nghĩa toán học, mà chỉ đơn giản là để xác định số chữ số sau dấu thập phân. 1234567890.123, theo toán học có 13 chữ số chính xác hoặc chữ số có nghĩa. 0,000000000123 có 3 chữ số chính xác toán học, không phải 13. Các số dấu phẩy động được phân phối theo lôgarit. Câu trả lời này sử dụng các chữ số có nghĩa và cảm giác chính xác toán học .
chux - Phục hồi Monica

1
@Slipp D. Thompson "Có giá trị nhỏ nhất của thông số C và trình biên dịch C11 mẫu ."
Chux - Khôi phục Monica

1
Quả thực bạn nói đúng - thủ thuật của tôi chỉ hợp lệ cho các giá trị có độ lớn từ 1,0 đến 1,0eDBL_DIG, được cho là phạm vi duy nhất thực sự phù hợp để in "%f"ngay từ đầu. Sử dụng "%e"như bạn đã trình bày tất nhiên là một cách tiếp cận tốt hơn và có hiệu quả là một câu trả lời phù hợp (mặc dù có lẽ nó không tốt bằng việc sử dụng "%a"nếu nó có sẵn, và tất nhiên "%a"sẽ có sẵn nếu `DBL_DECIMAL_DIG). Tôi đã luôn mong muốn một công cụ định dạng sẽ luôn làm tròn chính xác với độ chính xác tối đa (thay vì 6 chữ số thập phân được mã hóa cứng).
Greg A. Woods

66

Câu trả lời ngắn gọn để in các số dấu phẩy động một cách dễ dàng (sao cho chúng có thể được đọc lại thành chính xác cùng một số, ngoại trừ NaN và Infinity):

  • Nếu loại của bạn là float: sử dụng printf("%.9g", number).
  • Nếu loại của bạn là double: sử dụng printf("%.17g", number).

KHÔNG sử dụng %f, vì điều đó chỉ xác định có bao nhiêu chữ số có nghĩa sau số thập phân và sẽ cắt bớt các số nhỏ. Để tham khảo, có thể tìm thấy số 9 và 17 kỳ diệu trong float.hđó xác định FLT_DECIMAL_DIGDBL_DECIMAL_DIG.


6
Bạn có thể giải thích các thông số kỹ thuật %gkhông?
Vilhelm Grey

14
% g in số với nhiều chữ số cần thiết để có độ chính xác, ưu tiên cú pháp hàm mũ khi các số nhỏ hoặc lớn (1e-5 thay vì .00005) và bỏ qua bất kỳ số 0 ở cuối nào (1 thay vì 1.00000).
ccxvii

4
@truthseeker Để biểu diễn mã IEEE 754 binary64, thực sự cần in ít nhất 15 vị trí số thập phân quan trọng. Nhưng rõ ràng-ness cần 17 vì độ chính xác thay đổi trong một số nhị phân (2,4,8, v.v.) và số thập phân (10,100,1000, v.v.) không bao giờ ở cùng một số (ngoại trừ 1,0). Ví dụ: 2 doublegiá trị trên 0.1: 1.000_0000_0000_0000_2e-01, 1.000_0000_0000_0000_3e-01cần 17 chữ số để phân biệt.
Chux - Khôi phục Monica

3
@chux - Bạn nhầm lẫn về hành vi của% .16g; nó không đủ để bạn làm ví dụ phân biệt 1.000_0000_0000_0000_2e-01 với 1.000_0000_0000_0000_3e-01. % .17g là cần thiết.
Don Hatch

1
@Don Hatch Tôi đồng ý "%.16g"là không đủ và "%.17g""%.16e"là đủ. Các chi tiết của %g, đã được tôi nhớ sai.
Chux - Khôi phục Monica

23

Nếu bạn chỉ quan tâm đến bit (mẫu hex tương ứng), bạn có thể sử dụng %ađịnh dạng. Điều này đảm bảo cho bạn:

Độ chính xác mặc định đủ cho một biểu diễn chính xác của giá trị nếu tồn tại một biểu diễn chính xác trong cơ số 2 và nếu không thì đủ lớn để phân biệt các giá trị kiểu double.

Tôi phải nói thêm rằng điều này chỉ có sẵn kể từ C99.


16

Không, không có thông số chiều rộng printf như vậy để in dấu phẩy động với độ chính xác tối đa . Hãy để tôi giải thích tại sao.

Độ chính xác tối đa của floatdoublethể thay đổi , và phụ thuộc vào giá trị thực của floathoặc double.

Nhớ lại floatdoubleđược lưu trữ ở định dạng sign.exponent.mantissa . Điều này có nghĩa là có nhiều bit hơn được sử dụng cho thành phần phân số đối với số nhỏ hơn là số lớn.

nhập mô tả hình ảnh ở đây

Ví dụ, floatcó thể dễ dàng phân biệt giữa 0,0 và 0,1.

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

Nhưng floatkhông có ý tưởng về sự khác biệt giữa 1e271e27 + 0.1.

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

Điều này là do tất cả độ chính xác (bị giới hạn bởi số bit định trị) được sử dụng hết cho phần lớn của số, bên trái của số thập phân.

Công cụ %.fsửa đổi chỉ cho biết bạn muốn in bao nhiêu giá trị thập phân từ số thực cho đến khi định dạng . Thực tế là độ chính xác có sẵn phụ thuộc vào kích thước của con sốtùy thuộc vào bạn với tư cách là người lập trình xử lý. printfkhông thể / không xử lý điều đó cho bạn.


2
Đây là một lời giải thích tuyệt vời về những hạn chế của việc in chính xác các giá trị dấu phẩy động đến các chữ số thập phân cụ thể. Tuy nhiên, tôi tin rằng tôi đã quá mơ hồ với lựa chọn từ ngữ ban đầu của mình, vì vậy tôi đã cập nhật câu hỏi của mình để tránh cụm từ "độ chính xác tối đa" với hy vọng rằng nó có thể giải tỏa sự nhầm lẫn.
Vilhelm Grey

Nó vẫn phụ thuộc vào giá trị của số bạn đang in.
bobobobo

3
điều này đúng một phần, nhưng nó không trả lời câu hỏi và bạn bối rối không biết OP đang hỏi gì. Anh ấy đang hỏi liệu người ta có thể truy vấn số lượng chữ số [thập phân] quan trọng mà a floatcung cấp hay không và bạn khẳng định rằng không có điều đó (tức là không có FLT_DIG), điều này là sai.

@ H2CO3 Có lẽ bạn nên chỉnh sửa bài đăng của tôi và downvote (j / k). Câu trả lời này khẳng định FLT_DIGkhông có nghĩa gì cả. Câu trả lời này khẳng định số lượng vị trí thập phân có sẵn phụ thuộc vào giá trị bên trong float .
bobobobo 23/12/13

1
Bạn có giả sử ký tự định dạng phải là "f" không? Tôi không nghĩ rằng điều đó là bắt buộc. Đọc câu hỏi của tôi là OP đang tìm kiếm một số thông số định dạng printf để tạo ra một chuyến đi khứ hồi không tổn thất, vì vậy câu trả lời của @ccxvii ("% .9g" cho float, "% .17g" cho double) là một một trong những tốt. Có lẽ câu hỏi sẽ tốt hơn bằng cách loại bỏ từ "chiều rộng" khỏi nó.
Don Hatch

11

Chỉ cần sử dụng các macro từ <float.h>và công cụ chỉ định chuyển đổi chiều rộng thay đổi ( ".*"):

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

2
@OliCharlesworth Bạn có muốn nói như vậy không:printf("%." FLT_DIG "f\n", f);
Vilhelm Grey,

3
+1 nhưng điều này hoạt động tốt nhất %e, không quá tốt cho %f: chỉ khi biết rằng giá trị cần in là gần với 1.0.
Pascal Cuoq,

3
%ein các chữ số có nghĩa cho các số rất nhỏ và %fkhông. vd x = 1e-100. %.5fbản in 0.00000(mất tổng số lần tuế sai). %.5ebản in 1.00000e-100.
chux - Phục hồi Monica

1
@bobobobo Ngoài ra, bạn sai ở chỗ nó "mang lại nhiều lý do chính xác hơn". FLT_DIGđược xác định cho giá trị mà nó được xác định vì một lý do. Nếu là 6, đó là vì floatkhông thể có độ chính xác nhiều hơn 6 chữ số. Nếu bạn in nó bằng cách sử dụng %.7f, chữ số cuối cùng sẽ không có nghĩa. Hãy suy nghĩ trước khi bạn phản đối.

5
@bobobobo Không, %.6fkhông phải là tương đương, vì FLT_DIGkhông phải lúc nào cũng là 6. Và ai quan tâm đến hiệu quả? I / O đã quá đắt đỏ, độ chính xác của một chữ số nhiều hơn hoặc ít hơn sẽ không tạo ra nút thắt cổ chai.

5

Tôi chạy một thử nghiệm nhỏ để xác minh rằng việc in với DBL_DECIMAL_DIGthực sự bảo toàn chính xác biểu diễn nhị phân của số. Hóa ra đối với các trình biên dịch và thư viện C mà tôi đã thử, DBL_DECIMAL_DIGthực sự là số chữ số được yêu cầu, và việc in với thậm chí ít hơn một chữ số sẽ tạo ra một vấn đề nghiêm trọng.

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

Tôi chạy điều này bằng trình biên dịch C của Microsoft 19.00.24215.1 và phiên bản gcc 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1). Sử dụng một chữ số thập phân nhỏ hơn một nửa số lượng các số so sánh chính xác bằng nhau. (Tôi cũng đã xác minh rằng rand()khi được sử dụng thực sự tạo ra khoảng một triệu con số khác nhau.) Dưới đây là kết quả chi tiết.

Microsoft C

Đã kiểm tra 999507 giá trị có 17 chữ số: 999507 tìm thấy số bằng nhau, 999507 tìm thấy nhị phân bằng nhau
Đã kiểm tra 999507 giá trị có 16 chữ số: 545389 tìm thấy số bằng nhau, 545389 tìm thấy nhị phân bằng nhau

GCC

Đã kiểm tra 999485 giá trị có 17 chữ số: 999485 tìm thấy số bằng nhau, 999485 tìm thấy nhị phân bằng nhau
Đã kiểm tra 999485 giá trị có 16 chữ số: 545402 tìm thấy số bằng nhau, 545402 tìm thấy nhị phân bằng nhau

1
"chạy điều này với trình biên dịch C của Microsoft" -> Trình biên dịch đó có thể có RAND_MAX == 32767. Cân nhắc u.s[j] = (rand() << 8) ^ rand();hoặc tương tự để đảm bảo tất cả các bit đều có cơ hội là 0 hoặc 1.
chux - Phục hồi Monica

Thật vậy, RAND_MAX của nó là 32767, vì vậy đề xuất của bạn chính xác.
Diomidis Spinellis

1
Tôi đã cập nhật bài đăng để xử lý RAND_MAX theo đề xuất của @ chux-ReinstateMonica. Kết quả tương tự như kết quả thu được trước đó.
Diomidis Spinellis

3

Trong một trong những nhận xét của tôi cho một câu trả lời, tôi đã than thở rằng từ lâu tôi đã muốn có cách nào đó để in tất cả các chữ số có nghĩa dưới dạng giá trị dấu phẩy động ở dạng thập phân, giống như cách mà câu hỏi yêu cầu. Cuối cùng thì tôi cũng đã ngồi xuống và viết nó. Nó không hoàn toàn hoàn hảo và đây là mã demo in thêm thông tin, nhưng nó chủ yếu hoạt động cho các thử nghiệm của tôi. Vui lòng cho tôi biết nếu bạn (tức là bất kỳ ai) muốn một bản sao của toàn bộ chương trình trình bao bọc để điều khiển nó để thử nghiệm.

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

Tôi không quan tâm liệu nó có trả lời câu hỏi hay không - điều này thực sự ấn tượng. Nó đã được một số suy nghĩ và cần được thừa nhận và khen ngợi. Có lẽ sẽ tốt nếu bạn bao gồm bằng cách nào đó (dù ở đây hay cách khác) toàn bộ mã để thử nghiệm nhưng ngay cả khi không có nó thì đây thực sự là một công việc tốt. Có một +1 cho điều đó!
Pryftan

0

Theo hiểu biết của tôi, có một thuật toán cũng khuếch tán cho phép để sản xuất với số cần thiết của chữ số có nghĩa như vậy mà khi quét chuỗi lại, bản gốc giá trị dấu chấm động được mua lại trong dtoa.cđược viết bởi Daniel Gay, trong đó có sẵn ở đây trên Netlib (xem cũng là giấy liên quan ). Mã này được sử dụng, ví dụ như trong Python, MySQL, Scilab và nhiều mã khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.