2013-03-04 4 views
6

मैं सी के लिए नया हूं और इंटरनेट से एक पुस्तक/बाहर सीख रहा हूं। मैं एक ऐसा फ़ंक्शन लिखने की कोशिश कर रहा हूं जिसे मैं double पास कर सकता हूं और को printf("%.*lf" ... कथन में उपयोग करने के लिए वापस लौटा सकता हूं, जैसे कि int लौटाया गया न तो परिशुद्धता को कम करेगा और न ही पिछला शून्य उत्पन्न करेगा।सी गतिशील रूप से printf डबल, परिशुद्धता का कोई नुकसान नहीं और कोई पिछला शून्य शून्य

मेरे पास एक कामकाजी कार्य है लेकिन यह काफी बड़ा है क्योंकि यह पठनीयता के लिए लिखा गया है और सभी ने टिप्पणी की है।

समारोह संक्षेप में, मैं गिनती कैसे 10 से कई डिवीजनों यह रेंज 10 > d >= 0 में double पाने के लिए लेता है, केवल आंशिक भाग लेने और n दशमलव स्थानों के साथ एक string में डाल दिया जहाँ 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

आप 'स्प्रिंटफ (लाइन,"%। * एफ ", पीएल, डी) का उपयोग कर सकते हैं;' 'स्विच 'के बजाय। –

उत्तर

8

शायद कोई आसान तरीका नहीं है। यह एक काफी शामिल समस्या है।

आपका कोड कई कारणों से सही इसे सुलझाने नहीं है:

  • फ्लोटिंग प्वाइंट अंकगणित दशमलव में नहीं कर रहे हैं के अधिकांश व्यावहारिक कार्यान्वयन, वे बाइनरी हैं। इसलिए, जब आप फ्लोटिंग-पॉइंट नंबर 10 से गुणा करते हैं या इसे 10 तक विभाजित करते हैं, तो आप सटीकता खो सकते हैं (यह संख्या पर निर्भर करता है)।
  • हालांकि मानक 64-bit IEEE-754 फ्लोटिंग प्वाइंट प्रारूप भंडार अपूर्णांश, जो floor(log10(2^53)) = 15 दशमलव अंक के बराबर है के लिए 53 बिट्स, इस प्रारूप में एक वैध संख्या कुछ 1080 दशमलव अंक के लिए आंशिक में आवश्यकता हो सकती है भाग जब बिल्कुल मुद्रित होता है, तो आप यही पूछते हैं।

इस के हल के लिए एक तरह से snprintf() में %a प्रारूप प्रकार निर्दिष्टकर्ता, जो अपूर्णांश और 1999 की गारंटी देता है कि यह सब प्रिंट होगा से सी मानक के लिए हेक्साडेसिमल अंक का उपयोग कर फ़्लोटिंग-बिंदु मान मुद्रित करने के लिए जा रहा है उपयोग करने के लिए है महत्वपूर्ण अंक यदि फ्लोटिंग-पॉइंट प्रारूप रेडिक्स -2 (एकेए बेस -2 या बस बाइनरी) है। तो, इसके साथ आप संख्या के मंटिसा के सभी द्विआधारी अंक प्राप्त कर सकते हैं। और यहां से आप यह पता लगाने में सक्षम होंगे कि आंशिक भाग में कितने दशमलव अंक हैं।

अब, देखें कि:

1।00000 = 2 +0 = 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

पूरी तरह से उत्तर के लिए धन्यवाद। ऐसा लगता है कि प्रिंटिंग युगल में इस प्रयास में काफी कटौती हुई है। मैंने बस अपना कोड पढ़ने की कोशिश कर बहुत कुछ सीखा है। शायद जब मैं अपनी सी पुस्तक पढ़ना समाप्त कर दूंगा तो यह मेरे लिए थोड़ा और स्पष्ट हो जाएगा। क्यों, ओह, मैंने नीली गोली क्यों नहीं ली? – JB0x2D1

+0

क्या आप जानते हैं कि मेरे कंपाइलर का प्रिंटफ़ सटीक द्वारा अनुरोध किए गए अंकों को सही तरीके से प्रिंट करता है या नहीं? जीसीसी --वर्जन जीसीसी (उबंटू 4.4.3-4ubuntu5.1) 4.4.3 – JB0x2D1

+0

आप कोशिश क्यों नहीं करते और देखते हैं? सुनिश्चित करें कि आप '-std = c99' भाग को नहीं भूलते क्योंकि gcc एक गैर-सी 99 मोड में डिफ़ॉल्ट होगा, जिसमें चीजें काफी काम नहीं कर सकती हैं (वे विंडोज़ पर मिनीजीडब्ल्यू से मेरे जीसीसी 4.6.2 के साथ नहीं हैं I '-std = c99' का उपयोग करें)। यदि आपको उत्तर में समान आउटपुट मिलता है, तो सब ठीक है। –

5

पहली बात मैं नोटिस कि आप 10 द्वारा temp विभाजित कर रहे हैं और कि परिशुद्धता का नुकसान हो रहा है है।

आपको बंद करने या फिर से प्रयास करने से आपको हतोत्साहित नहीं करना है, लेकिन इसका सही कार्यान्वयन आपके द्वारा दिखाए गए कार्यों की तुलना में काफी अधिक शामिल है।

गाय एल स्टील और जॉन एल। व्हाइट ने "How to print floating-point numbers accurately" नामक एक पेपर लिखा जो कुछ नुकसान का विवरण देता है और फ्लोटिंग-पॉइंट नंबर प्रिंट करने के लिए एक कार्यरत एल्गोरिदम प्रस्तुत करता है। यह एक अच्छा पढ़ा है।

+0

धन्यवाद। मैं इसे एक लुक दूंगा। – JB0x2D1

+0

आप लिंक 403 त्रुटि की ओर जाता है। – Ruslan

+0

@Ruslan: Bah! यह पीएलडीआई '96 से है अगर यह आपको बिल्कुल मदद करता है। एक पीडीएफ के लिए एक और लिंक खोजने की कोशिश कर रहा है, लेकिन मैं खाली आ रहा हूँ। अगर आपको ऑनलाइन प्रतिलिपि मिलती है तो इसे संपादित करने के लिए स्वतंत्र महसूस करें। – tmyklebu

संबंधित मुद्दे