سؤال

ما زلت أتعثر في محددات التنسيق لعائلة وظائف printf().ما أريده هو أن أكون قادرًا على طباعة رقم مزدوج (أو عائم) بحد أقصى لعدد معين من الأرقام بعد العلامة العشرية.إذا كنت تستخدم:

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

انا حصلت

359.013
359.010

بدلا من المطلوب

359.013
359.01

أي شخص يمكن أن تساعدني؟

هل كانت مفيدة؟

المحلول

لا يمكن أن يتم ذلك مع المختصين شكل printf العادية. سيكون الأقرب يمكن أن تحصل هي:

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

ولكن "0.6" هو مجموعه عرض رقمي لذلك

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

وقوعه.

ما <م> يمكن القيام به هو sprintf("%.20g") الرقم إلى المخزن المؤقت سلسلة ثم التلاعب السلسلة إلى نملك سوى أحرف N الماضي الفاصلة العشرية.

وعلى افتراض رقمك في الأسطوانات المتغيرة، فإن الدالة التالية إزالة جميع ولكن عشرى N الأولى، ثم تجريدها من الأصفار زائدة (والعشرية إذا كانت كافة الأصفار).

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

إذا كنت غير راض عن هذا الجانب اقتطاع (والذي من شأنه أن يحول 0.12399 إلى 0.123 بدلا من التقريب إلى 0.124)، يمكنك فعلا استخدام مرافق التقريب التي قدمتها بالفعل printf. تحتاج فقط لتحليل العدد قبل يد لخلق حيوي الاعراض، ثم استخدام هذه لتحويل عدد إلى سلسلة:

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

وبيت القصيد من nDecimals() في هذه الحالة هو العمل بشكل صحيح من الاعراض المجال، ثم تنسيق الرقم باستخدام سلسلة تنسيق بناء على ذلك. يظهر main() اختبار تسخير في هذا العمل:

  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

وبمجرد الانتهاء من القيمة بشكل صحيح مدورة، يمكنك تمرير مرة أخرى أن لmorphNumericString() لإزالة الأصفار الزائدة ببساطة عن طريق تغيير:

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

في:

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

و(أو الدعوة morphNumericString في نهاية nDecimals ولكن، في هذه الحالة، ربما كنت الجمع بين اثنين فقط في وظيفة واحدة)، وينتهي بك الأمر مع:

  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

نصائح أخرى

لتخلص من الأصفار زائدة، يجب عليك استخدام "٪ ز" الشكل:

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

وبعد السؤال وتوضيح بعض الشيء، أن قمع الأصفار ليست هي الشيء الوحيد الذي سئل، ولكن الحد من الإخراج إلى ثلاث خانات عشرية كان مطلوبا أيضا. أعتقد أن لا يمكن القيام به مع سلاسل التنسيق sprintf وحدها. كما أشار باكس ديابلو بها، ستكون هناك حاجة التلاعب السلسلة.

تعجبني إجابة ر.مُعدل قليلاً:

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

"1000" يحدد الدقة.

القدرة على التقريب 0.5.

يحرر

حسنًا، تم تعديل هذه الإجابة عدة مرات وفقدت تتبع ما كنت أفكر فيه قبل بضع سنوات (وفي الأصل لم تملأ جميع المعايير).إذن إليك إصدار جديد (يملأ جميع المعايير ويتعامل مع الأرقام السالبة بشكل صحيح):

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

وحالات الاختبار:

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

ومخرجات الاختبارات:

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

الآن تم استيفاء جميع المعايير:

  • الحد الأقصى لعدد الكسور العشرية خلف الصفر ثابت
  • تتم إزالة الأصفار الزائدة
  • يفعل ذلك بشكل صحيح رياضيا (أليس كذلك؟)
  • يعمل (الآن) أيضًا عندما يكون الرقم العشري الأول صفرًا

لسوء الحظ هذه الإجابة عبارة عن خطين sprintf لا يعود السلسلة.

وأنا ابحث في سلسلة (ابتداء من أقصى اليمين) للحرف الأول في 1 مجموعة ل9 (ASCII قيمة 49-57) ثم null (مجموعة ل0) كل حق شار منه - انظر أدناه:

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

وماذا عن شيء مثل هذا (قد يكون أخطاء التقريب والقضايا قيمة سالبة، التي تحتاج إلى تصحيح، غادر باعتبارها ممارسة للقارئ):

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

وانها برنامجي قليلا إلا أنها على الأقل لا يجعلك تفعل أي تلاعب السلسلة.

وهناك حل بسيط لكنه يحصل على هذه المهمة، يعين طول المعروفة والدقة والابتعاد عن فرصة للصعود الشكل الأسي (الذي يشكل خطرا عند استخدام٪ ز):

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

وإضافة أو إزالة "ELSES" إذا كنت تريد بحد اقصى 2 عشرية. 4 العشرية. وما إلى ذلك.

وعلى سبيل المثال إذا كنت تريد 2 الكسور العشرية:

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

إذا كنت ترغب في تحديد الحد الأدنى للعرض للحفاظ على الحقول الانحياز، زيادة عند الضرورة، على سبيل المثال:

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

ويمكنك أيضا تحويل ذلك إلى وظيفة حيث يمكنك تمرير العرض المطلوب من الملعب:

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

ولقد وجدت مشاكل في بعض الحلول التي تم نشرها. أنا وضعت هذا معا على أساس الإجابات أعلاه. ويبدو أن العمل بالنسبة لي.

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

وبعض الحلول صوت عالية اقتراح محدد تحويل %g من printf. وهذا خطأ لأن هناك حالات حيث %g سينتج العلمي. حلول أخرى تستخدم الرياضيات في طباعة العدد المطلوب من الأرقام العشرية.

وأعتقد أن الحل الأسهل هو استخدام sprintf مع محدد تحويل %f وإزالة الأصفار الزائدة وربما العشرية من النتيجة يدويا. وهنا حل 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;
}

لاحظ أن الحروف المستخدمة للأرقام والفاصل العشري تعتمد على لغة الحالية. رمز أعلاه يفترض لغة C أو الإنكليزية.

وهنا هو أول محاولة في الإجابة:

void
xprintfloat(char *format, float f)
{
  char s[50];
  char *p;

  sprintf(s, format, f);
  for(p=s; *p; ++p)
    if('.' == *p) {
      while(*++p);
      while('0'==*--p) *p = '\0';
    }
  printf("%s", s);
}

والحشرات المعروفة: ممكن تجاوز سعة المخزن المؤقت اعتمادا على الشكل. إذا "." هي موجودة لسبب سوى٪ و قد يحدث نتيجة خاطئة.

لماذا لا نفعل ذلك؟

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

اختلاف طفيف عما سبق:

  1. يزيل الفترة للحالة (10000.0).
  2. فترات راحة بعد معالجة الفترة الأولى.

الكود هنا:

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

لا يزال لديه احتمال التجاوز، لذا كن حذرًا؛P

يتم تقريب الكود الخاص بك إلى ثلاث منازل عشرية بسبب ".3" قبل f

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

وبالتالي، إذا قمت بتقريب السطر الثاني إلى منزلتين عشريتين، فيجب عليك تغييره إلى هذا:

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

سيُخرج هذا الرمز النتائج المرجوة:

359.013
359.01

*لاحظ أن هذا على افتراض أنك قمت بالفعل بالطباعة على أسطر منفصلة، ​​وإذا لم يكن الأمر كذلك، فإن ما يلي سيمنعها من الطباعة على نفس السطر:

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

كان الكود المصدري للبرنامج التالي هو اختباري لهذه الإجابة

#include <cstdio>

int main()
{

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

    while (true){}

    return 0;

}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top