2013-03-04 45 views
6

我是C新手,學會了一本書/關閉互聯網。我試圖編寫一個函數,我可以通過任何double並返回int用於printf("%.*lf" ...語句中,使得返回的int既不會降低精度也不會產生尾隨零。C動態printf雙精度不丟失,沒有尾隨零

我有一個工作函數,但它是相當大的,因爲它是爲了可讀性和全部註釋而編寫的。

總結功能,我算需要多少部門由10獲得範圍10 > d >= 0double,只取小數部分,並把它變成一個string有n位小數,其中n = 15 - number_of_digits_left_of_decimal(我讀的類型double只能跟蹤15位數字),請從右向左檢查string以查找尾隨零並保持計數,最後返回代表小數右邊非零數字的數字的int

有沒有更簡單的方法?謝謝。

int get_number_of_digits_after_decimal(double d) 
{ 
    int i = 0;  /* sometimes you need an int */ 
    int pl = 0;  /* precision left = 15 - sigfigs */ 
    int sigfigs = 1; /* the number of digits in d */ 
    char line[20]; /* used to find last non-zero digit right of the decimal place */ 
    double temp; /* a copy of d used for destructive calculations */ 

    /* find digits to right of decimal */ 
    temp = d; 
    while(sigfigs < 15) 
    { 
    if(temp < 0) 
     temp *= -1; 
    if(temp < 10) 
     break; 
    temp /= 10; 
    ++sigfigs; 
    } 
    /* at this point 10 > temp >= 0 
    * decrement temp unitl 1 > temp >=0 */ 
    while(temp > 1) 
    { 
    --temp; 
    } 
    if(temp == 0) 
    return(0); 
    pl = 15 - sigfigs; /* if n digits left of decimal, 15-n to right */ 
    switch(pl) 
    { 
    case 14: 
    sprintf(line, "%.14lf", d); 
    break; 
    case 13: 
    sprintf(line, "%.13lf", d); 
    break; 
    case 12: 
    sprintf(line, "%.12lf", d); 
    break; 
    case 11: 
    sprintf(line, "%.11lf", d); 
    break; 
    case 10: 
    sprintf(line, "%.10lf", d); 
    break; 
    case 9: 
    sprintf(line, "%.9f", d); 
    break; 
    case 8: 
    sprintf(line, "%.8lf", d); 
    break; 
    case 7: 
    sprintf(line, "%.7lf", d); 
    break; 
    case 6: 
    sprintf(line, "%.6lf", d); 
    break; 
    case 5: 
    sprintf(line, "%.5lf", d); 
    break; 
    case 4: 
    sprintf(line, "%.4lf", d); 
    break; 
    case 3: 
    sprintf(line, "%.3lf", d); 
    break; 
    case 2: 
    sprintf(line, "%.2lf", d); 
    break; 
    case 1: 
    sprintf(line, "%.1lf", d); 
    break; 
    case 0: 
    return(0); 
    break; 
    } 
    i = (strlen(line) - 1); /* last meaningful digit char */ 
    while(1) /* start at end of string, move left checking for first non-zero */ 
    { 
    if(line[i] == '0') /* if 0 at end */ 
    { 
     --i; 
     --pl; 
    } 
    else 
    { 
     break; 
    } 
    } 
    return(pl); 
} 
+2

您可以使用'sprintf(line,「%。* f」,pl,d);'而不是'switch'。 – 2013-03-04 05:18:55

回答

8

可能沒有簡單的方法。這是一個相當棘手的問題。

你的代碼是不是解決它的權利有以下幾個原因:

  • 浮點運算是不是小數的大多數實際實現,它們是二進制。因此,當您將浮點數乘以10或將其除以10時,可能會失去精度(這取決於數字)。
  • 即使標準64-bit IEEE-754浮點格式儲量的尾數,這相當於floor(log10(2^53)) = 15十進制數字53位,在該格式的有效數量可能需要多達一些1080十進制數字的小數部分打印完全正確,這是你似乎在問的問題。

解決的方法之一是使用snprintf()%a格式類型說明符,這是會使用尾數和1999年的保證,這將打印所有的C標準的十六進制數字打印的浮點值如果浮點格式爲基數2(AKA base-2或簡單二進制),則爲有效數字。所以,用這個你可以獲得數字尾數的所有二進制數字。從這裏你可以知道小數部分有多少個小數位。

現在,請注意:

1。00000 = 2 = 1.00000(二進制)
0.50000 = 2 -1 = 0.10000
0.25000 = 2 -2 = 0.01000
0.12500 = 2 -3 = 0.00100
0.06250 = 2 -4 = 0.00010
0.03125 = 2 -5 = 0.00001

等。

你可以清楚地看到這裏,在i個位置中的二進制表示的點右邊二進制數字產生的最後一個非零十進制數字也i個位置的右側十進制表示中的點。因此,如果您知道二進制浮點數中最低有效非零位的位置,則可以計算出精確打印數字的小數部分所需的小數位數。

這就是我的程序正在做的事情。

代碼:

// file: PrintFullFraction.c 
// 
// compile with gcc 4.6.2 or better: 
// gcc -Wall -Wextra -std=c99 -O2 PrintFullFraction.c -o PrintFullFraction.exe 
#include <limits.h> 
#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 
#include <math.h> 
#include <float.h> 
#include <assert.h> 

#if FLT_RADIX != 2 
#error currently supported only FLT_RADIX = 2 
#endif 

int FractionalDigits(double d) 
{ 
    char buf[ 
      1 + // sign, '-' or '+' 
      (sizeof(d) * CHAR_BIT + 3)/4 + // mantissa hex digits max 
      1 + // decimal point, '.' 
      1 + // mantissa-exponent separator, 'p' 
      1 + // mantissa sign, '-' or '+' 
      (sizeof(d) * CHAR_BIT + 2)/3 + // exponent decimal digits max 
      1 // string terminator, '\0' 
      ]; 
    int n; 
    char *pp, *p; 
    int e, lsbFound, lsbPos; 

    // convert d into "+/- 0x h.hhhh p +/- ddd" representation and check for errors 
    if ((n = snprintf(buf, sizeof(buf), "%+a", d)) < 0 || 
     (unsigned)n >= sizeof(buf)) 
    return -1; 

//printf("{%s}", buf); 

    // make sure the conversion didn't produce something like "nan" or "inf" 
    // instead of "+/- 0x h.hhhh p +/- ddd" 
    if (strstr(buf, "0x") != buf + 1 || 
     (pp = strchr(buf, 'p')) == NULL) 
    return 0; 

    // extract the base-2 exponent manually, checking for overflows 
    e = 0; 
    p = pp + 1 + (pp[1] == '-' || pp[1] == '+'); // skip the exponent sign at first 
    for (; *p != '\0'; p++) 
    { 
    if (e > INT_MAX/10) 
     return -2; 
    e *= 10; 
    if (e > INT_MAX - (*p - '0')) 
     return -2; 
    e += *p - '0'; 
    } 
    if (pp[1] == '-') // apply the sign to the exponent 
    e = -e; 

//printf("[%s|%d]", buf, e); 

    // find the position of the least significant non-zero bit 
    lsbFound = lsbPos = 0; 
    for (p = pp - 1; *p != 'x'; p--) 
    { 
    if (*p == '.') 
     continue; 
    if (!lsbFound) 
    { 
     int hdigit = (*p >= 'a') ? (*p - 'a' + 10) : (*p - '0'); // assuming ASCII chars 
     if (hdigit) 
     { 
     static const int lsbPosInNibble[16] = { 0,4,3,4, 2,4,3,4, 1,4,3,4, 2,4,3,4 }; 
     lsbFound = 1; 
     lsbPos = -lsbPosInNibble[hdigit]; 
     } 
    } 
    else 
    { 
     lsbPos -= 4; 
    } 
    } 
    lsbPos += 4; 

    if (!lsbFound) 
    return 0; // d is 0 (integer) 

    // adjust the least significant non-zero bit position 
    // by the base-2 exponent (just add them), checking 
    // for overflows 

    if (lsbPos >= 0 && e >= 0) 
    return 0; // lsbPos + e >= 0, d is integer 

    if (lsbPos < 0 && e < 0) 
    if (lsbPos < INT_MIN - e) 
     return -2; // d isn't integer and needs too many fractional digits 

    if ((lsbPos += e) >= 0) 
    return 0; // d is integer 

    if (lsbPos == INT_MIN && -INT_MAX != INT_MIN) 
    return -2; // d isn't integer and needs too many fractional digits 

    return -lsbPos; 
} 

const double testData[] = 
{ 
    0, 
    1, // 2^0 
    0.5, // 2^-1 
    0.25, // 2^-2 
    0.125, 
    0.0625, // ... 
    0.03125, 
    0.015625, 
    0.0078125, // 2^-7 
    1.0/256, // 2^-8 
    1.0/256/256, // 2^-16 
    1.0/256/256/256, // 2^-24 
    1.0/256/256/256/256, // 2^-32 
    1.0/256/256/256/256/256/256/256/256, // 2^-64 
    3.14159265358979323846264338327950288419716939937510582097494459, 
    0.1, 
    INFINITY, 
#ifdef NAN 
    NAN, 
#endif 
    DBL_MIN 
}; 

int main(void) 
{ 
    unsigned i; 
    for (i = 0; i < sizeof(testData)/sizeof(testData[0]); i++) 
    { 
    int digits = FractionalDigits(testData[i]); 
    assert(digits >= 0); 
    printf("%f %e %.*f\n", testData[i], testData[i], digits, testData[i]); 
    } 
    return 0; 
} 

輸出(ideone):

0.000000 0.000000e+00 0 
1.000000 1.000000e+00 1 
0.500000 5.000000e-01 0.5 
0.250000 2.500000e-01 0.25 
0.125000 1.250000e-01 0.125 
0.062500 6.250000e-02 0.0625 
0.031250 3.125000e-02 0.03125 
0.015625 1.562500e-02 0.015625 
0.007812 7.812500e-03 0.0078125 
0.003906 3.906250e-03 0.00390625 
0.000015 1.525879e-05 0.0000152587890625 
0.000000 5.960464e-08 0.000000059604644775390625 
0.000000 2.328306e-10 0.00000000023283064365386962890625 
0.000000 5.421011e-20 0.0000000000000000000542101086242752217003726400434970855712890625 
3.141593 3.141593e+00 3.141592653589793115997963468544185161590576171875 
0.100000 1.000000e-01 0.1000000000000000055511151231257827021181583404541015625 
inf inf inf 
nan nan nan 
0.000000 2.225074e-308 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201383090232717332404064219215980462331830553327416887204434813918195854283159012511020564067339731035811005152434161553460108856

你可以看到,π0.1是唯一真正達到15十進制數字,其餘的這些數字顯示了這些數字真正起了什麼作用因爲這些數字不能完全用二進制浮點格式表示。

你也可以看到,DBL_MIN,最小正正規化double值,在小數部分1022數字和那些有715顯著數字。

這種解決方案可能出現的問題:

  • 編譯器的printf()功能不支持%a或不正確打印的精度要求的所有數字(這是很可能的)。
  • 您的計算機使用非二進制浮點格式(這是非常罕見的)。
+0

謝謝你的詳細解答。我似乎在打印雙打方面進行了相當多的嘗試。我學習了很多,只是想讀你的代碼。也許當我讀完C書後,它會變得更加清晰。爲什麼,哦,爲什麼,我沒有服用藍色藥丸? – JB0x2D1 2013-03-05 03:30:56

+0

你知道我的編譯器的printf是否能正確打印精度要求的數字嗎? gcc --version gcc(Ubuntu 4.4.3-4ubuntu5.1)4.4.3 – JB0x2D1 2013-03-05 03:43:28

+0

你爲什麼不試試看?確保你不要忘記'-std = c99'部分,因爲gcc將默認爲非C99模式,在這種模式下,事情可能不會起作用(它們不與我在Windows上的MinGW使用gcc 4.6.2,除非我使用'-std = c99')。如果你的答案和答案一樣,一切都很好。 – 2013-03-05 03:56:34

5

我注意到的第一件事是,你將temp通過10而造成的精度損失。

不要關閉你或者阻止你再次嘗試,但是正確實現這個要比你展示的要多得多。

Guy L. Steele和Jon L. White編寫了一篇名爲「How to print floating-point numbers accurately」的論文,詳細介紹了一些陷阱並提出了一種用於打印浮點數的工作算法。這是一個很好的閱讀。

+0

謝謝。我會看看。 – JB0x2D1 2013-03-05 03:31:21

+0

您鏈接到錯誤403. – Ruslan 2015-06-02 10:12:22

+0

@Ruslan:Bah!它來自PLDI'96,如果這對你有所幫助。試圖找到另一個PDF的鏈接,但我空空如也。如果您找到在線副本,請隨時編輯。 – tmyklebu 2015-06-02 14:18:16