Tránh các số 0 ở cuối trong printf ()


107

Tôi tiếp tục vấp phải các chỉ định định dạng cho họ hàm printf (). Điều tôi muốn là có thể in một double (hoặc float) với số chữ số cho trước tối đa sau dấu thập phân. Nếu tôi sử dụng:

printf("%1.3f", 359.01335);
printf("%1.3f", 359.00999);

tôi có

359.013
359.010

Thay vì mong muốn

359.013
359.01

Ai có thể giúp tôi?


1
Tính không chính xác của dấu chấm động thực sự có nghĩa là bạn nên tự làm tròn số. Lấy một biến thể của câu trả lời R và Juha (không xử lý được các số 0 ở cuối) và sửa nó.
wnoise

Câu trả lời:


88

Điều này không thể được thực hiện với các printfchỉ định định dạng thông thường . Gần nhất bạn có thể nhận được sẽ là:

printf("%.6g", 359.013); // 359.013
printf("%.6g", 359.01);  // 359.01

nhưng ".6" là tổng chiều rộng số nên

printf("%.6g", 3.01357); // 3.01357

phá vỡ nó.

Những gì bạn có thể làm là nhập sprintf("%.20g")số vào bộ đệm chuỗi sau đó thao tác để chuỗi chỉ có N ký tự vượt qua dấu thập phân.

Giả sử số của bạn nằm trong biến num, hàm sau sẽ xóa tất cả trừ các Nsố thập phân đầu tiên , sau đó loại bỏ các số không ở cuối (và dấu thập phân nếu chúng đều là số không).

char str[50];
sprintf (str,"%.20g",num);  // Make the number.
morphNumericString (str, 3);
:    :
void morphNumericString (char *s, int n) {
    char *p;
    int count;

    p = strchr (s,'.');         // Find decimal point, if any.
    if (p != NULL) {
        count = n;              // Adjust for more or less decimals.
        while (count >= 0) {    // Maximum decimals allowed.
             count--;
             if (*p == '\0')    // If there's less than desired.
                 break;
             p++;               // Next character.
        }

        *p-- = '\0';            // Truncate string.
        while (*p == '0')       // Remove trailing zeros.
            *p-- = '\0';

        if (*p == '.') {        // If all decimals were zeros, remove ".".
            *p = '\0';
        }
    }
}

Nếu bạn không hài lòng với khía cạnh cắt bớt (sẽ chuyển 0.12399thành 0.123thay vì làm tròn thành 0.124), bạn thực sự có thể sử dụng các phương tiện làm tròn đã được cung cấp bởi printf. Bạn chỉ cần phân tích số trước đó để tự động tạo độ rộng, sau đó sử dụng chúng để biến số thành chuỗi:

#include <stdio.h>

void nDecimals (char *s, double d, int n) {
    int sz; double d2;

    // Allow for negative.

    d2 = (d >= 0) ? d : -d;
    sz = (d >= 0) ? 0 : 1;

    // Add one for each whole digit (0.xx special case).

    if (d2 < 1) sz++;
    while (d2 >= 1) { d2 /= 10.0; sz++; }

    // Adjust for decimal point and fractionals.

    sz += 1 + n;

    // Create format string then use it.

    sprintf (s, "%*.*f", sz, n, d);
}

int main (void) {
    char str[50];
    double num[] = { 40, 359.01335, -359.00999,
        359.01, 3.01357, 0.111111111, 1.1223344 };
    for (int i = 0; i < sizeof(num)/sizeof(*num); i++) {
        nDecimals (str, num[i], 3);
        printf ("%30.20f -> %s\n", num[i], str);
    }
    return 0;
}

Toàn bộ điểm nDecimals()trong trường hợp này là tính toán chính xác độ rộng trường, sau đó định dạng số bằng cách sử dụng chuỗi định dạng dựa trên đó. Khai thác thử nghiệm main()cho thấy điều này trong hoạt động:

  40.00000000000000000000 -> 40.000
 359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.010
 359.00999999999999090505 -> 359.010
   3.01357000000000008200 -> 3.014
   0.11111111099999999852 -> 0.111
   1.12233439999999995429 -> 1.122

Khi bạn có giá trị được làm tròn chính xác, một lần nữa bạn có thể chuyển giá trị đó morphNumericString()để loại bỏ các số không ở cuối bằng cách thay đổi:

nDecimals (str, num[i], 3);

thành:

nDecimals (str, num[i], 3);
morphNumericString (str, 3);

(hoặc gọi morphNumericStringở cuối nDecimalsnhưng, trong trường hợp đó, tôi có thể chỉ kết hợp cả hai thành một hàm), và bạn kết thúc bằng:

  40.00000000000000000000 -> 40
 359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.01
 359.00999999999999090505 -> 359.01
   3.01357000000000008200 -> 3.014
   0.11111111099999999852 -> 0.111
   1.12233439999999995429 -> 1.122

8
Lưu ý, câu trả lời này giả định rằng vị trí thập phân là .Trong một số ngôn ngữ, vị trí thập phân thực sự là một ,dấu phẩy.

1
Thực sự có một lỗi đánh máy nhỏ ở đây - p = strchr (str, '.'); thực sự phải là p = strchr (s, '.'); Để sử dụng hàm param thay vì var toàn cục.
n3wtz

Sử dụng quá nhiều chữ số có thể gây ra kết quả không mong muốn ... ví dụ "% .20g" với 0,1 sẽ hiển thị rác. Điều bạn cần làm nếu không muốn làm người dùng ngạc nhiên là bắt đầu từ 15 chữ số và tiếp tục tăng dần cho đến khi atoftrả về cùng một giá trị.
6502

@ 6502, đó là bởi vì không có thứ như 0,1 trong IEEE754 :-) Tuy nhiên, nó sẽ không gây ra vấn đề (ít nhất là đối với giá trị đó) vì kết quả 0.10000000000000000555sẽ là 0.1khi bị loại bỏ. Vấn đề có thể xảy ra nếu bạn có giá trị hơi thấp hơn, chẳng hạn như nếu đại diện gần nhất của 42.142.099999999314159. Nếu bạn thực sự muốn xử lý điều đó, thì bạn có thể cần phải làm tròn dựa trên chữ số cuối cùng bị loại bỏ thay vì cắt bớt.
paxdiablo

1
@paxdiablo: Không cần phải tạo động một chuỗi định dạng cho các printfhàm -like vì chúng đã hỗ trợ các tham số động ( *ký tự đặc biệt): ví dụ: printf("%*.*f", total, decimals, x);xuất ra một số với tổng độ dài trường và số thập phân được chỉ định động.
6502

54

Để loại bỏ các số không ở cuối, bạn nên sử dụng định dạng "% g":

float num = 1.33;
printf("%g", num); //output: 1.33

Sau khi câu hỏi được làm rõ một chút, rằng việc loại bỏ các số không không phải là điều duy nhất được yêu cầu, nhưng giới hạn đầu ra ở ba chữ số thập phân cũng được yêu cầu. Tôi nghĩ rằng điều đó không thể được thực hiện chỉ với chuỗi định dạng sprintf. Như Pax Diablo đã chỉ ra, thao tác chuỗi sẽ được yêu cầu.



@Tomalak: Điều này không làm những gì tôi muốn. Tôi muốn có thể chỉ định số chữ số tối đa sau dấu thập phân. Đây là: Tôi muốn 1.3347 được in "1.335" và 1.3397 được in "1.34" @xtofl: Tôi đã kiểm tra điều đó, nhưng tôi vẫn không thể thấy câu trả lời cho vấn đề của mình
Gorpik

3
Tác giả muốn phong cách 'f' không phải phong cách 'e'. 'g' có thể sử dụng kiểu "e": Từ tài liệu: Kiểu e được sử dụng nếu số mũ từ chuyển đổi của nó nhỏ hơn -4 hoặc lớn hơn hoặc bằng độ chính xác.
Julien Palard

20

Tôi thích câu trả lời của R. đã được điều chỉnh một chút:

float f = 1234.56789;
printf("%d.%.0f", f, 1000*(f-(int)f));

'1000' xác định độ chính xác.

Power đến 0,5 làm tròn.

BIÊN TẬP

Ok, câu trả lời này đã được chỉnh sửa một vài lần và tôi đã mất dấu những gì tôi đang nghĩ vài năm trước (và ban đầu nó không đáp ứng tất cả các tiêu chí). Vì vậy, đây là một phiên bản mới (điền vào tất cả các tiêu chí và xử lý các số âm một cách chính xác):

double f = 1234.05678900;
char s[100]; 
int decimals = 10;

sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
printf("10 decimals: %d%s\n", (int)f, s+1);

Và các trường hợp thử nghiệm:

#import <stdio.h>
#import <stdlib.h>
#import <math.h>

int main(void){

    double f = 1234.05678900;
    char s[100];
    int decimals;

    decimals = 10;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf("10 decimals: %d%s\n", (int)f, s+1);

    decimals = 3;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" 3 decimals: %d%s\n", (int)f, s+1);

    f = -f;
    decimals = 10;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" negative 10: %d%s\n", (int)f, s+1);

    decimals = 3;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" negative  3: %d%s\n", (int)f, s+1);

    decimals = 2;
    f = 1.012;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" additional : %d%s\n", (int)f, s+1);

    return 0;
}

Và kết quả của các bài kiểm tra:

 10 decimals: 1234.056789
  3 decimals: 1234.057
 negative 10: -1234.056789
 negative  3: -1234.057
 additional : 1.01

Bây giờ, tất cả các tiêu chí đều được đáp ứng:

  • số thập phân tối đa phía sau số 0 được cố định
  • số không ở cuối bị xóa
  • nó đúng về mặt toán học (phải không?)
  • cũng hoạt động (bây giờ) khi số thập phân đầu tiên bằng 0

Thật không may, câu trả lời này là một hai chữ lót vì sprintfkhông trả về chuỗi.


1
Tuy nhiên, điều này dường như không thực sự cắt bớt các số 0 ở cuối? Nó có thể vui vẻ phun ra thứ gì đó giống như 1.1000.
Dean J

Câu hỏi và câu trả lời của tôi không phải là bản gốc nữa ... Tôi sẽ kiểm tra sau.
Juha

1
Các trường hợp kiểm tra không thành công: 1.012 với định dạng "% .2g" sẽ cho kết quả là 1.012 thay vì 1.01.
wtl

@wtl Bắt hay lắm. Hiện đã được sửa.
Juha

@Jeroen: Tôi nghĩ float cũng không thành công tại một số thời điểm khi số lượng lớn hơn ... nhưng về cơ bản việc đánh máy thành long, long long hoặc int64 sẽ hoạt động.
Juha

2

Tôi tìm kiếm trong chuỗi (bắt đầu từ ngoài cùng bên phải) cho ký tự đầu tiên trong phạm vi 1thành 9(giá trị ASCII 49- 57) sau đó null(đặt thành 0) mỗi ký tự bên phải của nó - xem bên dưới:

void stripTrailingZeros(void) { 
    //This finds the index of the rightmost ASCII char[1-9] in array
    //All elements to the left of this are nulled (=0)
    int i = 20;
    unsigned char char1 = 0; //initialised to ensure entry to condition below

    while ((char1 > 57) || (char1 < 49)) {
        i--;
        char1 = sprintfBuffer[i];
    }

    //null chars left of i
    for (int j = i; j < 20; j++) {
        sprintfBuffer[i] = 0;
    }
}

2

Còn những thứ như thế này thì sao (có thể có lỗi làm tròn và các vấn đề về giá trị âm cần gỡ lỗi, để lại như một bài tập cho người đọc):

printf("%.0d%.4g\n", (int)f/10, f-((int)f-(int)f%10));

Nó hơi có lập trình nhưng ít nhất nó không khiến bạn thực hiện bất kỳ thao tác chuỗi nào.


1

Một giải pháp đơn giản nhưng hoàn thành công việc, chỉ định độ dài và độ chính xác đã biết và tránh cơ hội chuyển sang định dạng hàm mũ (đây là rủi ro khi bạn sử dụng% g):

// Since we are only interested in 3 decimal places, this function
// can avoid any potential miniscule floating point differences
// which can return false when using "=="
int DoubleEquals(double i, double j)
{
    return (fabs(i - j) < 0.000001);
}

void PrintMaxThreeDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%.1f", d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%.2f", d);
    else
        printf("%.3f", d);
}

Thêm hoặc xóa "elses" nếu bạn muốn có tối đa 2 số thập phân; 4 số thập phân; Vân vân.

Ví dụ: nếu bạn muốn có 2 số thập phân:

void PrintMaxTwoDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%.1f", d);
    else
        printf("%.2f", d);
}

Nếu bạn muốn chỉ định chiều rộng tối thiểu để giữ cho các trường được căn chỉnh, hãy tăng số nếu cần, ví dụ:

void PrintAlignedMaxThreeDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%7.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%9.1f", d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%10.2f", d);
    else
        printf("%11.3f", d);
}

Bạn cũng có thể chuyển đổi nó thành một hàm mà bạn chuyển độ rộng mong muốn của trường:

void PrintAlignedWidthMaxThreeDecimal(int w, double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%*.0f", w-4, d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%*.1f", w-2, d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%*.2f", w-1, d);
    else
        printf("%*.3f", w, d);
}

Lấy d = 0.0001: thì floor(d)0, vì vậy sự khác biệt lớn hơn 0,000001, vì vậy DoubleEqualslà sai, vì vậy nó sẽ không sử dụng từ "%.0f"chỉ định: bạn sẽ thấy các số không ở cuối từ "%*.2f"hoặc "%*.3f". Vì vậy, nó không trả lời câu hỏi.
Cœur

1

Tôi đã tìm thấy vấn đề trong một số giải pháp đã đăng. Tôi kết hợp điều này với nhau dựa trên các câu trả lời ở trên. Nó dường như làm việc cho tôi.

int doubleEquals(double i, double j) {
    return (fabs(i - j) < 0.000001);
}

void printTruncatedDouble(double dd, int max_len) {
    char str[50];
    int match = 0;
    for ( int ii = 0; ii < max_len; ii++ ) {
        if (doubleEquals(dd * pow(10,ii), floor(dd * pow(10,ii)))) {
            sprintf (str,"%f", round(dd*pow(10,ii))/pow(10,ii));
            match = 1;
            break;
        }
    }
    if ( match != 1 ) {
        sprintf (str,"%f", round(dd*pow(10,max_len))/pow(10,max_len));
    }
    char *pp;
    int count;
    pp = strchr (str,'.');
    if (pp != NULL) {
        count = max_len;
        while (count >= 0) {
             count--;
             if (*pp == '\0')
                 break;
             pp++;
        }
        *pp-- = '\0';
        while (*pp == '0')
            *pp-- = '\0';
        if (*pp == '.') {
            *pp = '\0';
        }
    }
    printf ("%s\n", str);
}

int main(int argc, char **argv)
{
    printTruncatedDouble( -1.999, 2 ); // prints -2
    printTruncatedDouble( -1.006, 2 ); // prints -1.01
    printTruncatedDouble( -1.005, 2 ); // prints -1
    printf("\n");
    printTruncatedDouble( 1.005, 2 ); // prints 1 (should be 1.01?)
    printTruncatedDouble( 1.006, 2 ); // prints 1.01
    printTruncatedDouble( 1.999, 2 ); // prints 2
    printf("\n");
    printTruncatedDouble( -1.999, 3 ); // prints -1.999
    printTruncatedDouble( -1.001, 3 ); // prints -1.001
    printTruncatedDouble( -1.0005, 3 ); // prints -1.001 (shound be -1?)
    printTruncatedDouble( -1.0004, 3 ); // prints -1
    printf("\n");
    printTruncatedDouble( 1.0004, 3 ); // prints 1
    printTruncatedDouble( 1.0005, 3 ); // prints 1.001
    printTruncatedDouble( 1.001, 3 ); // prints 1.001
    printTruncatedDouble( 1.999, 3 ); // prints 1.999
    printf("\n");
    exit(0);
}

1

Một số giải pháp được bình chọn cao đề xuất chỉ số %gchuyển đổi của printf. Điều này sai vì có những trường hợp%g sẽ tạo ra ký hiệu khoa học. Các giải pháp khác sử dụng toán học để in ra số chữ số thập phân mong muốn.

Tôi nghĩ giải pháp đơn giản nhất là sử dụng sprintfcông %fcụ xác định chuyển đổi và xóa thủ công các số không ở cuối và có thể là dấu thập phân khỏi kết quả. Đây là giải pháp C99:

#include <stdio.h>
#include <stdlib.h>

char*
format_double(double d) {
    int size = snprintf(NULL, 0, "%.3f", d);
    char *str = malloc(size + 1);
    snprintf(str, size + 1, "%.3f", d);

    for (int i = size - 1, end = size; i >= 0; i--) {
        if (str[i] == '0') {
            if (end == i + 1) {
                end = i;
            }
        }
        else if (str[i] == '.') {
            if (end == i + 1) {
                end = i;
            }
            str[end] = '\0';
            break;
        }
    }

    return str;
}

Lưu ý rằng các ký tự được sử dụng cho các chữ số và dấu phân tách thập phân phụ thuộc vào ngôn ngữ hiện tại. Đoạn mã trên giả định ngôn ngữ C hoặc tiếng Anh Mỹ.


−0.00005 và 3 số thập phân sẽ mang lại "−0" có thể mong muốn hoặc không. Ngoài ra, có thể đặt "3" thành một tham số để thuận tiện cho những người khác?
dolphin

0

Đây là lần đầu tiên tôi thử câu trả lời:

vô hiệu
xprintfloat (định dạng char *, float f)
{
  char s [50];
  ký tự * p;

  sprintf (s, định dạng, f);
  cho (p = s; * p; ++ p)
    if ('.' == * p) {
      while (* ++ p);
      while ('0' == * - p) * p = '\ 0';
    }
  printf ("% s", s);
}

Các lỗi đã biết: Có thể xảy ra tràn bộ đệm tùy thuộc vào định dạng. Nếu "." hiện tại vì lý do khác ngoài% f kết quả sai có thể xảy ra.


Các lỗi đã biết của bạn giống với bản thân printf (), vì vậy không có vấn đề gì ở đó. Nhưng tôi đang tìm kiếm một chuỗi định dạng cho phép tôi làm những gì tôi muốn, không phải một giải pháp lập trình, đó là những gì tôi đã có.
Gorpik

0

Tại sao không chỉ làm điều này?

double f = 359.01335;
printf("%g", round(f * 1000.0) / 1000.0);

0

Biến thể nhẹ ở trên:

  1. Loại bỏ khoảng thời gian cho trường hợp (10000.0).
  2. Các khoảng ngắt sau khoảng thời gian đầu tiên được xử lý.

Mã ở đây:

void EliminateTrailingFloatZeros(char *iValue)
{
  char *p = 0;
  for(p=iValue; *p; ++p) {
    if('.' == *p) {
      while(*++p);
      while('0'==*--p) *p = '\0';
      if(*p == '.') *p = '\0';
      break;
    }
  }
}

Nó vẫn tiềm ẩn nguy cơ tràn, vì vậy hãy cẩn thận; P


0

Tôi sẽ nói bạn nên sử dụng printf("%.8g",value);

Nếu bạn sử dụng, "%.6g"bạn sẽ không nhận được đầu ra mong muốn cho một số số như 32.230210 nó sẽ in 32.23021nhưng nó in32.2302


-2

Mã của bạn làm tròn đến ba chữ số thập phân do dấu ".3" trước f

printf("%1.3f", 359.01335);
printf("%1.3f", 359.00999);

Vì vậy, nếu bạn làm tròn dòng thứ hai thành hai chữ số thập phân, bạn nên thay đổi nó thành thế này:

printf("%1.3f", 359.01335);
printf("%1.2f", 359.00999);

Mã đó sẽ xuất ra kết quả mong muốn của bạn:

359.013
359.01

* Lưu ý rằng điều này giả sử bạn đã có nó in trên các dòng riêng biệt, nếu không có thì những điều sau sẽ ngăn nó in trên cùng một dòng:

printf("%1.3f\n", 359.01335);
printf("%1.2f\n", 359.00999);

Mã nguồn của chương trình sau là bài kiểm tra của tôi cho câu trả lời này

#include <cstdio>

int main()
{

    printf("%1.3f\n", 359.01335);
    printf("%1.2f\n", 359.00999);

    while (true){}

    return 0;

}

4
Câu hỏi là về việc tạo một chuỗi định dạng bỏ qua các số không ở cuối, không có các chuỗi định dạng khác nhau cho từng trường hợp.
Christoph Walesch 24/09/13
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.