¿Cómo restrinjo un valor flotante a solo dos lugares después del punto decimal en C?

215

¿Cómo puedo redondear un valor flotante (como 37.777779) a dos decimales (37.78) en C?

Sakib Arifin
fuente
15
No puede redondear correctamente el número en sí, porque float(y double) no son coma flotante decimal, son coma flotante binaria, por lo que redondear a posiciones decimales no tiene sentido. Sin embargo, puede redondear la salida.
Pavel Minaev
63
No tiene sentido; Es inexacto. Hay una gran diferencia
Brooks Moses
2
¿Qué tipo de redondeo estás esperando? Half-up o Redondeo al par más cercano?
Truthseeker Rangwan

Respuestas:

407

Si solo desea redondear el número para fines de salida, la "%.2f"cadena de formato es la respuesta correcta. Sin embargo, si realmente desea redondear el valor de coma flotante para un mayor cálculo, algo como lo siguiente funciona:

#include <math.h>

float val = 37.777779;

float rounded_down = floorf(val * 100) / 100;   /* Result: 37.77 */
float nearest = roundf(val * 100) / 100;  /* Result: 37.78 */
float rounded_up = ceilf(val * 100) / 100;      /* Result: 37.78 */

Observe que hay tres reglas de redondeo diferentes que puede elegir: redondear hacia abajo (es decir, truncar después de dos decimales), redondear al más cercano y redondear hacia arriba. Por lo general, desea redondear al más cercano.

Como varios otros han señalado, debido a las peculiaridades de la representación de coma flotante, estos valores redondeados pueden no ser exactamente los valores decimales "obvios", pero estarán muy, muy cerca.

Para obtener más (¡mucho!) Más información sobre el redondeo, y especialmente sobre las reglas de desempate para el redondeo al más cercano, consulte el artículo de Wikipedia sobre el redondeo .

Dale Hagglund
fuente
44
¿Se puede modificar para soportar el redondeo a precisión arbitraria?
1
@slater Cuando dice 'precisión arbitraria', ¿está preguntando acerca del redondeo a, por ejemplo, tres en lugar de dos decimales, o el uso de bibliotecas que implementan valores decimales de precisión ilimitados? Si es lo primero, haga lo que espero sean ajustes obvios a la constante 100; de lo contrario, haga exactamente los mismos cálculos que se muestran arriba, solo con la biblioteca de precisión múltiple que esté utilizando.
Dale Hagglund
2
@DaleHagglung El primero, gracias. ¿Es el ajuste para reemplazar 100 con pow (10, (int) Precision deseada)?
3
Sí. Para redondear después de k decimales, use un factor de escala de 10 ^ k. Esto debería ser realmente fácil de ver si escribe algunos valores decimales a mano y juega con múltiplos de 10. Suponga que está trabajando con el valor 1.23456789 y desea redondearlo a 3 decimales. La operación disponible para usted es redonda a entera . Entonces, ¿cómo mueve los primeros tres decimales para que queden a la izquierda del punto decimal? Espero que esté claro que multiplicas por 10 ^ 3. Ahora puede redondear ese valor a un entero. Luego, vuelve a colocar los tres dígitos de orden inferior dividiéndolos por 10 ^ 3.
Dale Hagglund
1
¿Puedo hacer que esto funcione doublestambién de alguna manera? No parece hacer el trabajo que quiero :( (usando floory ceil).
Sra. Nobody
87

Usando % .2f en printf. Solo imprime 2 puntos decimales.

Ejemplo:

printf("%.2f", 37.777779);

Salida:

37.77
Andrew Coleson
fuente
De esta manera es mejor porque no hay pérdida de precisión.
albert
2
@albert Esto también tiene la ventaja de no perder el floatrango, ya que val * 100podría desbordarse.
chux - Restablece a Mónica el
42

Suponiendo que está hablando de redondear el valor de impresión, la respuesta de Andrew Coleson y AraK es correcta:

printf("%.2f", 37.777779);

Pero tenga en cuenta que si desea redondear el número exactamente a 37.78 para uso interno (por ejemplo, para compararlo con otro valor), entonces esta no es una buena idea, debido a la forma en que funcionan los números de coma flotante: generalmente no desea hacer comparaciones de igualdad para coma flotante, en su lugar use un valor objetivo +/- un valor sigma. O codifique el número como una cadena con una precisión conocida y compárelo.

Vea el enlace en la respuesta de Greg Hewgill a una pregunta relacionada , que también cubre por qué no debe usar coma flotante para los cálculos financieros.

John Carter
fuente
1
Votado por abordar cuál puede ser la pregunta detrás de la pregunta (¡o la pregunta que debería haber estado detrás de la pregunta!). Ese es un punto bastante importante.
Brooks Moses
En realidad, 37.78 se pueden presentar exactamente por punto flotante. El flotador tiene de 11 a 12 dígitos para mayor precisión. Eso debería ser suficiente para abordar 3778 377.8 o todo tipo de 4 dígitos decimales.
Anónimo White
@HaryantoCiu, sí, he editado mi respuesta un poco.
John Carter
precisión dinámica:printf("%.*f", (int)precision, (double)number);
Minhas Kamal
24

Qué tal esto:

float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);
Daniil
fuente
44
-1: a) esto no funcionará para números negativos (ok, el ejemplo es positivo pero aún así). b) no mencionas que es imposible almacenar el valor decimal exacto en el flotante
John Carter
32
@therefromhere: (a) Tienes razón (b) ¿Qué es esto? ¿Una prueba de secundaria?
Daniil
1
¿Por qué agregaste 0.5?
muhammad tayyab
1
Es necesario seguir las reglas de redondeo.
Daniil
1
las reglas de redondeo en el contexto del comentario de @Daniil son redondeadas al más cercano
Shmil The Cat
20
printf("%.2f", 37.777779);

Si quieres escribir en C-string:

char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);
AraK
fuente
@Sinan: ¿Por qué la edición? @AraK: No, se debe cuidar el tamaño :). Use snprintf ().
aib
1
@aib: Supongo que / ** / son comentarios de estilo C y la pregunta está etiquetada para C
Michael Haren
55
C89 solo permitió / ** / - estilo, C99 introdujo soporte para // - estilo. Use un compilador cojo / antiguo (o forzar el modo C89) y no podrá usar el estilo //. Habiendo dicho eso, es 2009, consideremos ambos estilo C y C ++.
Andrew Coleson el
11

No hay forma de redondear un floata otro floatporque el redondeado floatpuede no ser representable (una limitación de los números de coma flotante). Por ejemplo, supongamos que redondea 37.777779 a 37.78, pero el número representable más cercano es 37.781.

Sin embargo, puede "redondear" a floatmediante una función de cadena de formato.

Andrew Keeton
fuente
3
Esto no es diferente de decir "no hay forma de dividir dos flotadores y obtener un flotador, porque el resultado dividido puede no ser representable", lo cual puede ser precisamente cierto pero es irrelevante. Los flotadores son siempre inexactos, incluso para algo tan básico como la suma; la suposición es siempre que lo que realmente obtienes es "el flotador que más se aproxima a la respuesta redondeada exacta".
Brooks Moses
Lo que quise decir es que no puedes redondear floata n decimales y luego esperar que el resultado siempre tenga n decimales. Todavía obtendrá un float, simplemente no el que esperaba.
Andrew Keeton
9

Además, si está utilizando C ++, puede crear una función como esta:

string prd(const double x, const int decDigits) {
    stringstream ss;
    ss << fixed;
    ss.precision(decDigits); // set # places after decimal
    ss << x;
    return ss.str();
}

Luego puede generar cualquier doble myDoublecon nlugares después del punto decimal con un código como este:

std::cout << prd(myDouble,n);
synaptik
fuente
7

Aún puedes usar:

float ceilf(float x); // don't forget #include <math.h> and link with -lm.

ejemplo:

float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;
ZeroCool
fuente
Esto se trunca en el punto decimal (es decir, producirá 37), y necesita redondear a dos lugares después del punto decimal.
Pavel Minaev
Sin embargo, redondear a dos lugares después del punto decimal es una variación trivial (pero aún debe mencionarse en la respuesta; ZeroCool, ¿desea agregar una edición?): Float roundedValue = ceilf (valueToRound * 100.0) / 100.0;
Brooks Moses
En un estado de sueño :)
ZeroCool
¿Cómo es que esta solución no es más popular? Esto funciona exactamente como debería con un código mínimo. ¿Hay alguna advertencia con eso?
Andy
7

En C ++ (o en C con conversiones de estilo C), puede crear la función:

/* Function to control # of decimal places to be output for x */
double showDecimals(const double& x, const int& numDecimals) {
    int y=x;
    double z=x-y;
    double m=pow(10,numDecimals);
    double q=z*m;
    double r=round(q);

    return static_cast<double>(y)+(1.0/m)*r;
}

Entonces std::cout << showDecimals(37.777779,2);produciría: 37.78.

Obviamente, realmente no necesita crear las 5 variables en esa función, pero las dejo allí para que pueda ver la lógica. Probablemente haya soluciones más simples, pero esto funciona bien para mí, especialmente porque me permite ajustar la cantidad de dígitos después del decimal, según sea necesario.

synaptik
fuente
5

Utilice siempre la printffamilia de funciones para esto. Incluso si desea obtener el valor como flotante, es mejor usarlo snprintfpara obtener el valor redondeado como una cadena y luego analizarlo de nuevo con atof:

#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

double dround(double val, int dp) {
    int charsNeeded = 1 + snprintf(NULL, 0, "%.*f", dp, val);
    char *buffer = malloc(charsNeeded);
    snprintf(buffer, charsNeeded, "%.*f", dp, val);
    double result = atof(buffer);
    free(buffer);
    return result;
}

Digo esto porque el enfoque que se muestra en la respuesta actualmente más votada y en varias otras aquí, multiplicando por 100, redondeando al entero más cercano y luego dividiendo por 100 nuevamente, tiene fallas de dos maneras:

  • Para algunos valores, se redondeará en la dirección incorrecta porque la multiplicación por 100 cambia el dígito decimal que determina la dirección de redondeo de 4 a 5 o viceversa, debido a la imprecisión de los números de coma flotante.
  • Para algunos valores, multiplicar y luego dividir por 100 no redondea, lo que significa que incluso si no se realiza el redondeo, el resultado final será incorrecto

Para ilustrar el primer tipo de error, la dirección de redondeo a veces es incorrecta, intente ejecutar este programa:

int main(void) {
    // This number is EXACTLY representable as a double
    double x = 0.01499999999999999944488848768742172978818416595458984375;

    printf("x: %.50f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.50f\n", res1);
    printf("Rounded with round, then divided: %.50f\n", res2);
}

Verás esta salida:

x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406

Tenga en cuenta que el valor con el que comenzamos fue menor que 0.015, por lo que la respuesta matemáticamente correcta al redondearlo a 2 decimales es 0.01. Por supuesto, 0.01 no es exactamente representable como un doble, pero esperamos que nuestro resultado sea el doble más cercano a 0.01. Usar snprintfnos da ese resultado, pero usar round(100 * x) / 100nos da 0.02, lo cual está mal. ¿Por qué? Porque 100 * xnos da exactamente 1.5 como resultado. Multiplicar por 100 cambia la dirección correcta para redondear.

Para ilustrar el segundo tipo de error - el resultado a veces equivocarse debido a * 100y / 100no ser verdaderamente inversas entre sí - podemos hacer un ejercicio similar con un número muy grande:

int main(void) {
    double x = 8631192423766613.0;

    printf("x: %.1f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.1f\n", res1);
    printf("Rounded with round, then divided: %.1f\n", res2);
}

Nuestro número ahora ni siquiera tiene una parte fraccional; es un valor entero, solo almacenado con tipo double. Entonces, el resultado después de redondearlo debería ser el mismo número con el que comenzamos, ¿verdad?

Si ejecuta el programa anterior, verá:

x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0

Ups Nuestro snprintfmétodo devuelve el resultado correcto nuevamente, pero el enfoque de multiplicar, redondear y luego dividir falla. Esto se debe a que el valor matemáticamente correcto de 8631192423766613.0 * 100, 863119242376661300.0no es exactamente representable como un doble; el valor más cercano es 863119242376661248.0. Cuando divide eso por 100, obtiene 8631192423766612.0un número diferente al que comenzó.

Esperemos que sea una demostración suficiente de que el uso roundfpara redondear a un número de decimales está roto, y que debería usar snprintfen su lugar. Si eso te parece un truco horrible, quizás te tranquilice saber que es básicamente lo que hace CPython .

Mark Amery
fuente
+1 para un ejemplo concreto de lo que sale mal con mi respuesta y las similares, gracias a la rareza del punto flotante IEEE, y brindando una alternativa directa. Era periféricamente consciente, hace mucho tiempo, del gran esfuerzo que se imprimió y de mis amigos, seguros para los valores de punto flotante de ida y vuelta. Supongo que el trabajo realizado entonces podría estar apareciendo aquí.
Dale Hagglund
Ejem ... Perdón por la palabra ensalada cerca del final, que ahora es demasiado tarde para editar. Lo que quise decir fue "... un gran esfuerzo puesto en printf y amigos para hacerlos seguros ..."
Dale Hagglund
4

Uso float roundf(float x).

"Las funciones de redondeo redondean su argumento al valor entero más cercano en formato de punto flotante, redondeando a la mitad los casos desde cero, independientemente de la dirección de redondeo actual". C11dr §7.12.9.5

#include <math.h>
float y = roundf(x * 100.0f) / 100.0f; 

Dependiendo de su floatimplementación, los números que pueden parecer intermedios no lo son. como punto flotante es típicamente orientado a base 2. Además, redondear con precisión al más cercano 0.01en todos los casos "intermedios" es el más difícil.

void r100(const char *s) {
  float x, y;
  sscanf(s, "%f", &x);
  y = round(x*100.0)/100.0;
  printf("%6s %.12e %.12e\n", s, x, y);
}

int main(void) {
  r100("1.115");
  r100("1.125");
  r100("1.135");
  return 0;
}

 1.115 1.115000009537e+00 1.120000004768e+00  
 1.125 1.125000000000e+00 1.129999995232e+00
 1.135 1.134999990463e+00 1.139999985695e+00

Aunque "1.115" está "a mitad de camino" entre 1.11 y 1.12, cuando se convierte a float, el valor es 1.115000009537...y ya no es "a mitad de camino", sino más cercano a 1.12 y se redondea al más cercano floatde1.120000004768...

"1.125" es "mitad de camino" entre 1.12 y 1.13, cuando se convierte a float, el valor es exactamente 1.125y es "mitad de camino". Se redondea hacia 1.13 debido a lazos para gobernar incluso y se redondea al más cercano floatde1.129999995232...

Aunque "1.135" está "a mitad de camino" entre 1.13 y 1.14, cuando se convierte a float, el valor es 1.134999990463...y ya no es "a mitad de camino", sino más cercano a 1.13 y se redondea al más cercano floatde1.129999995232...

Si se usa el código

y = roundf(x*100.0f)/100.0f;

Aunque "1,135" es "a medio camino" entre 1,13 y 1,14, cuando se convierte a float, el valor es 1.134999990463...y ya no es "medio-way", pero más cerca de 1.13 pero incorrectamente rondas a floatde 1.139999985695...debido a la más limitada precisión de floatvs. double. Este valor incorrecto puede verse como correcto, dependiendo de los objetivos de codificación.

chux - Restablece a Monica
fuente
4

Hice esta macro para redondear números flotantes. Agréguelo en su encabezado / ser de archivo

#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))

Aquí hay un ejemplo:

float x = ROUNDF(3.141592, 100)

x es igual a 3.14 :)

mou
fuente
Esto se trunca, pero la pregunta requiere redondeo. Además, está sujeto a errores de redondeo en operaciones de punto flotante.
Eric Postpischil
3
double f_round(double dval, int n)
{
    char l_fmtp[32], l_buf[64];
    char *p_str;
    sprintf (l_fmtp, "%%.%df", n);
    if (dval>=0)
            sprintf (l_buf, l_fmtp, dval);
    else
            sprintf (l_buf, l_fmtp, dval);
    return ((double)strtod(l_buf, &p_str));

}

Aquí nestá el número de decimales

ejemplo:

double d = 100.23456;

printf("%f", f_round(d, 4));// result: 100.2346

printf("%f", f_round(d, 2));// result: 100.23
usuario2331026
fuente
-1 por cuatro razones: 1) la falta de explicación, 2) la vulnerabilidad al desbordamiento del búfer: esto se desbordará y, por lo tanto, posiblemente se bloqueará, si dvales enorme 3) el extraño if/ elsebloque donde haces exactamente lo mismo en cada rama y 4) el uso excesivamente complicado de sprintfpara construir el especificador de formato para una segunda sprintfllamada; es más simple usar .*y pasar el doble valor y el número de decimales como argumentos a la misma sprintfllamada.
Mark Amery el
3

Definición de código:

#define roundz(x,d) ((floor(((x)*pow(10,d))+.5))/pow(10,d))

Resultados:

a = 8.000000
sqrt(a) = r = 2.828427
roundz(r,2) = 2.830000
roundz(r,3) = 2.828000
roundz(r,5) = 2.828430
Privacidad de Zyp
fuente
0

Permítanme primero intentar justificar mi razón para agregar otra respuesta a esta pregunta. En un mundo ideal, el redondeo no es realmente un gran problema. Sin embargo, en sistemas reales, es posible que deba lidiar con varios problemas que pueden resultar en redondeo que puede no ser lo que espera. Por ejemplo, es posible que esté realizando cálculos financieros donde los resultados finales se redondean y se muestran a los usuarios como 2 lugares decimales; estos mismos valores se almacenan con precisión fija en una base de datos que puede incluir más de 2 decimales (por varias razones; no hay un número óptimo de lugares para guardar ... depende de situaciones específicas que cada sistema debe soportar, por ejemplo, artículos pequeños cuyos precios son fracciones de un centavo por unidad); y, cálculos de coma flotante realizados en valores donde los resultados son más / menos épsilon. He estado enfrentando estos problemas y desarrollando mi propia estrategia a lo largo de los años. No afirmaré que me he enfrentado a todos los escenarios o que tengo la mejor respuesta, pero a continuación hay un ejemplo de mi enfoque hasta ahora que supera estos problemas:

Suponga que 6 decimales se consideran precisión suficiente para los cálculos de flotantes / dobles (una decisión arbitraria para la aplicación específica), utilizando la siguiente función / método de redondeo:

double Round(double x, int p)
{
    if (x != 0.0) {
        return ((floor((fabs(x)*pow(double(10.0),p))+0.5))/pow(double(10.0),p))*(x/fabs(x));
    } else {
        return 0.0;
    }
}

El redondeo a 2 decimales para la presentación de un resultado se puede realizar como:

double val;
// ...perform calculations on val
String(Round(Round(Round(val,8),6),2));

Para val = 6.825, el resultado es 6.83como se esperaba.

Para val = 6.824999, el resultado es 6.82. Aquí la suposición es que el cálculo resultó exactamente 6.824999y el séptimo lugar decimal es cero.

Para val = 6.8249999, el resultado es 6.83. El séptimo lugar decimal 9en este caso hace que la Round(val,6)función dé el resultado esperado. Para este caso, podría haber cualquier número de 9s finales .

Para val = 6.824999499999, el resultado es 6.83. Redondeando al octavo lugar decimal como primer paso, es decir Round(val,8), se ocupa del único caso desagradable por el cual un resultado de coma flotante calculado se calcula 6.8249995, pero se representa internamente como 6.824999499999....

Finalmente, el ejemplo de la pregunta ... val = 37.777779da como resultado 37.78.

Este enfoque podría generalizarse aún más como:

double val;
// ...perform calculations on val
String(Round(Round(Round(val,N+2),N),2));

donde N es la precisión que se debe mantener para todos los cálculos intermedios en flotadores / dobles. Esto también funciona en valores negativos. No sé si este enfoque es matemáticamente correcto para todas las posibilidades.

Tim D
fuente
0

Código C simple para redondear un número:

float n = 3.56;
printf("%.f", n);

Esto generará:

4
Aqeel Shamsudheen
fuente
-1

... o puedes hacerlo a la antigua usanza sin ninguna biblioteca:

float a = 37.777779;

int b = a; // b = 37    
float c = a - b; // c = 0.777779   
c *= 100; // c = 77.777863   
int d = c; // d = 77;    
a = b + d / (float)100; // a = 37.770000;

Eso, por supuesto, si desea eliminar la información adicional del número.

NikosD
fuente
-2

esta función toma el número y la precisión y devuelve el número redondeado

float roundoff(float num,int precision)
{
      int temp=(int )(num*pow(10,precision));
      int num1=num*pow(10,precision+1);
      temp*=10;
      temp+=5;
      if(num1>=temp)
              num1+=10;
      num1/=10;
      num1*=10;
      num=num1/pow(10,precision+1);
      return num;
}

convierte el número de coma flotante en int desplazando el punto a la izquierda y verificando la condición mayor de cinco.

kapil
fuente