funcion - sintaxis de printf y scanf
Evite los ceros finales en printf() (12)
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);
yo obtengo
359.013
359.010
En lugar de lo deseado
359.013
359.01
Alguien puede ayudarme?
¿Por qué no solo hacer esto?
double f = 359.01335;
printf("%g", round(f * 1000.0) / 1000.0);
¿Qué pasa con algo como esto (puede tener errores de redondeo y de valor negativo que necesitan depuración, dejado como un ejercicio para el lector):
printf("%.0d%.4g/n", (int)f/10, f-((int)f-(int)f%10));
Es ligeramente programático, pero al menos no te obliga a manipular cadenas.
Algunas de las soluciones altamente votadas sugieren el especificador de conversión %g
de printf
. Esto es incorrecto porque hay casos donde %g
producirá notación científica. Otras soluciones usan matemática para imprimir la cantidad deseada 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 configuración regional actual. El código anterior supone una configuración en inglés C o US.
Aquí está mi primer intento en una 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 de búfer según el formato. Si "." está presente por alguna otra razón que no sea posible% f resultado incorrecto.
Busco la cuerda (comenzando más a la derecha) para el primer carácter en el rango de 1
a 9
(valor ASCII 49
- 57
) y luego null
(establecido en 0
) cada derecho de la carátula - 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;
}
}
Encontré problemas en algunas de las soluciones publicadas. Lo junté en base a las respuestas anteriores. Parece que funciona 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);
}
Esto no se puede hacer con los especificadores de formato printf
normales. Lo más cerca que podrías estar sería:
printf("%.6g", 359.013); // 359.013
printf("%.6g", 359.01); // 359.01
pero el ".6" es el ancho numérico total, por lo
printf("%.6g", 3.01357); // 3.01357
lo rompe
Lo que puedes hacer es sprintf("%.20g")
el número en un búfer de cadena y luego manipular la cadena para que solo tengan N caracteres más allá del punto decimal.
Suponiendo que su número está en la variable num, la siguiente función eliminará todas menos las primeras N
decimales, luego quitará los ceros finales (y el punto decimal si fueran todos 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
), puede utilizar las 0.12399
de redondeo que ya proporciona printf
. Solo necesita analizar el número de antemano para crear dinámicamente los anchos, y 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 formatee 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 redondeado correctamente, puede pasarlo a morphNumericString()
para eliminar los ceros finales simplemente cambiando:
nDecimals (str, num[i], 3);
dentro:
nDecimals (str, num[i], 3);
morphNumericString (str, 3);
(o llamando a morphNumericString
al final de nDecimals
pero, en ese caso, probablemente solo combine 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
Ligera variación de arriba: -
- Elimina el período para el caso (10000.0).
- Las pausas después del primer período se procesan.
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 ten cuidado; P
Me gusta la respuesta de R. levemente modificada:
float f = 1234.56789;
printf("%d.%.0f", f, 1000*(f-(int)f));
''1000'' determina la precisión.
Potencia al redondeo 0.5.
EDITAR
Bien, esta respuesta fue editada algunas veces y perdí la pista de lo que pensaba hace unos años (y originalmente no cumplía 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, todos los criterios se cumplen:
- el número máximo de decimales detrás del cero es fijo
- los ceros al final se eliminan
- lo hace matemáticamente correcto (¿verdad?)
- funciona (ahora) también cuando el primer decimal es cero
Lamentablemente, esta respuesta es de dos líneas, ya que sprintf
no devuelve la cadena.
Para deshacerse de los ceros finales, debe usar el formato "% g":
float num = 1.33;
printf("%g", num); //output: 1.33
Después de aclarar un poco la pregunta, suprimir ceros no es lo único que se solicitó, pero también se requirió que se limitara la salida a tres decimales. Creo que no se puede hacer solo con cadenas de formato sprintf. Como señaló Pax Diablo , se requeriría manipulación de cuerdas.
Tu código se redondea a tres lugares 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 lugares 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 es asumiendo que ya lo tiene 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;
}
Una solución simple pero que hace el trabajo, le asigna una longitud y precisión conocidas y evita la posibilidad de ir a un formato exponencial (que 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);
}
Agregue o elimine "elses" si quiere un máximo de 2 decimales; 4 decimales; etc.
Por ejemplo, si quieres 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 convertir eso 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);
}