Избегайте конечных нулей в printf()



Я продолжаю натыкаться на спецификаторы формата для семейства функций printf (). То, что я хочу, это иметь возможность печатать двойной (или плавающий) с максимальным заданным количеством цифр после десятичной точки. Если я использую:



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


Я



359.013
359.010


вместо желаемого



359.013
359.01


кто-нибудь может мне помочь?

646   12  

12 ответов:

это не может быть сделано с нормальным printf формат описателей. Самое близкое, что вы могли бы получить будет:

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

но ".6" является в общей сумме числовые ширина так

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

ломает ее.

что ты можете это sprintf("%.20g") число в строковый буфер, а затем манипулировать строке только N символов после десятичной точки.

предполагая, что ваш номер находится в переменной num, следующее функция удалит все, кроме первого 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 == '')    // If there's less than desired.
                 break;
             p++;               // Next character.
        }

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

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

если вы не довольны аспектом усечения (который превратится 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

чтобы избавиться от конечных нулей, вы должны использовать формат "%g":

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

после того, как вопрос был немного прояснен, что подавление нулей-это не единственное, что было задано, но также требовалось ограничить выход тремя десятичными знаками. Я думаю, что это не может быть сделано только с помощью строк формата sprintf. Как Pax Diablo указал, что потребуется манипуляция строками.

мне нравится ответ Р. слегка подправил:

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

'1000' определяет точность.

мощность до 0,5 округления.

EDIT

хорошо, этот ответ был отредактирован несколько раз, и я потерял то, что я думал несколько лет назад (и первоначально он не соответствовал всем критериям). Итак, вот новая версия (которая заполняет все критерии и правильно обрабатывает отрицательные числа):

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

это немного программно, но, по крайней мере, это не заставляет вас делать какие-либо строковые манипуляции.

простое решение, но оно выполняет работу, назначает известную длину и точность и избегает вероятности перехода в экспоненциальный формат (что является риском при использовании %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);
}

добавить или удалить "хуже", если вы хотите максимум 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);
}

вот моя первая попытка на ответ:

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 = '';
    }
  printf("%s", s);
}

известные ошибки: возможно переполнение буфера в зависимости от формата. Если." "присутствует по другой причине, чем %f может произойти неправильный результат.

почему бы просто не сделать это?

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

Я нашел проблемы в некоторых из опубликованных решений. Я собрал это вместе на основе ответов выше. Кажется, это работает для меня.

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 == '')
                 break;
             pp++;
        }
        *pp-- = '';
        while (*pp == '0')
            *pp-- = '';
        if (*pp == '.') {
            *pp = '';
        }
    }
    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] = '';
            break;
        }
    }

    return str;
}

обратите внимание, что символы, используемые для цифр и десятичного разделителя зависит от текущей локали. Приведенный выше код предполагает, C или американский английский язык.

небольшое изменение выше:

  1. исключает период для случая (10000.0).
  2. перерывы после первого периода обрабатывается.

вот код:

void EliminateTrailingFloatZeros(char *iValue)
{
  char *p = 0;
  for(p=iValue; *p; ++p) {
    if('.' == *p) {
      while(*++p);
      while('0'==*--p) *p = '';
      if(*p == '.') *p = '';
      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;

}

Comments

    Ничего не найдено.