Pregunta

Sigo tropezando con los especificadores de formato para la familia de funciones printf (). Lo que quiero es poder imprimir un doble (o flotante) con un número máximo de dígitos después del punto decimal. Si uso:

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

me sale

359.013
359.010

En lugar del deseado

359.013
359.01

¿Alguien puede ayudarme?

¿Fue útil?

Solución

Esto no se puede hacer con los especificadores de formato printf normales. Lo más cerca que podría estar sería:

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

pero el " .6 " es el total ancho numérico, así que

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

lo rompe.

Lo que puede hacer es sprintf ("%. 20g ") el número a un buffer de cadena y luego manipular la cadena para que solo tenga N caracteres más allá del decimal punto.

Suponiendo que su número está en la variable num, la siguiente función eliminará todos los decimales, excepto los primeros N , y luego eliminará los ceros finales (y el punto decimal si todos fueran ceros).

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

Si no está satisfecho con el aspecto de truncamiento (que convertiría 0.12399 en 0.123 en lugar de redondearlo a 0.124 ), en realidad puede usar las facilidades de redondeo ya proporcionadas por printf . Solo necesita analizar el número de antemano para crear dinámicamente los anchos, luego usarlos para convertir el número en una cadena:

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

El objetivo de nDecimals () en este caso es calcular correctamente los anchos de campo, luego formatear el número usando una cadena de formato basada en eso. El arnés de prueba main () muestra esto en acción:

  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

Una vez que tenga el valor correctamente redondeado, puede pasarlo una vez más a morphNumericString () para eliminar los ceros finales simplemente cambiando:

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

en:

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

(o llamando a morphNumericString al final de nDecimals pero, en ese caso, probablemente combinaría los dos en una función), y terminas con :

  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

Otros consejos

Para deshacerse de los ceros finales, debe usar el "% g " formato:

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

Después de aclarar un poco la pregunta, suprimir los ceros no es lo único que se preguntó, sino que también era necesario limitar la salida a tres decimales. Creo que eso no se puede hacer solo con cadenas de formato sprintf. Como Pax Diablo señaló, se requeriría la manipulación de cadenas.

Me gusta la respuesta de R. ligeramente modificada:

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

'1000' determina la precisión.

Potencia para el redondeo 0.5.

EDIT

Ok, esta respuesta fue editada varias veces y perdí la noción de lo que estaba pensando hace unos años (y originalmente no cumplía con todos los criterios). Así que aquí hay una nueva versión (que llena todos los criterios y maneja los números negativos correctamente):

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

Y los casos de prueba:

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

Y el resultado de las pruebas:

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

Ahora, se cumplen todos los criterios:

  • el número máximo de decimales detrás del cero es fijo
  • los ceros finales se eliminan
  • lo hace matemáticamente bien (¿verdad?)
  • funciona (ahora) también cuando el primer decimal es cero

Desafortunadamente, esta respuesta es de dos líneas ya que sprintf no devuelve la cadena.

Busco en la cadena (comenzando en el extremo derecho) el primer carácter en el rango 1 a 9 (valor ASCII 49 - 57 ) y luego null (establecido en 0 ) cada carácter a la derecha de la misma - ver a continuación:

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

¿Qué pasa con algo como esto (puede tener errores de redondeo y problemas de valor negativo que necesitan depuración, que se deja como ejercicio para el lector):

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

Es un poco programático, pero al menos no te obliga a manipular cadenas.

Una solución simple, pero hace el trabajo, asigna una longitud y precisión conocidas y evita la posibilidad de utilizar un formato exponencial (lo cual es un riesgo cuando usa% 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);
}

Añadir o eliminar " elses " si quieres un máximo de 2 decimales; 4 decimales; etc.

Por ejemplo, si desea 2 decimales:

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

Si desea especificar el ancho mínimo para mantener los campos alineados, incremente según sea necesario, por ejemplo:

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

También puede convertirlo en una función donde pase el ancho deseado del campo:

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

Encontré problemas en algunas de las soluciones publicadas. Puse esto juntos en base a las respuestas anteriores. Parece funcionar para mí.

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

Algunas de las soluciones altamente votadas sugieren el especificador de conversión % g de printf . Esto está mal porque hay casos en que % g producirá notación científica. Otras soluciones usan las matemáticas para imprimir el número deseado de dígitos decimales.

Creo que la solución más fácil es usar sprintf con el especificador de conversión % f y eliminar manualmente los ceros finales y posiblemente un punto decimal del resultado. Aquí hay una solución 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;
}

Tenga en cuenta que los caracteres utilizados para los dígitos y el separador decimal dependen de la ubicación actual. El código anterior supone una configuración regional de inglés C o inglés.

Aquí está mi primer intento de respuesta:

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

Errores conocidos: Posible desbordamiento del búfer según el formato. Si ". & Quot; está presente por otro motivo que no sea% f. Puede suceder un resultado incorrecto.

¿Por qué no solo hacer esto?

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

Ligera variación de lo anterior:

  1. Elimina el período para el caso (10000.0).
  2. Se rompe después de que se procesa el primer período.

Código aquí:

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

Todavía tiene potencial de desbordamiento, así que tenga cuidado; P

Su código se redondea a tres decimales debido a " .3 " antes de la f

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

Por lo tanto, si la segunda línea se redondea a dos decimales, debe cambiarla a esto:

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

Ese código generará los resultados deseados:

359.013
359.01

* Tenga en cuenta que esto supone que ya lo está imprimiendo en líneas separadas, de lo contrario, lo siguiente evitará que se imprima en la misma línea:

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

El siguiente código fuente del programa fue mi prueba para esta respuesta

#include <cstdio>

int main()
{

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

    while (true){}

    return 0;

}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top