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

11 minutos de lectura

¿Cómo puedo redondear un valor flotante (como 37,777779) a dos decimales (37,78) en C?

  • No puede redondear correctamente el número en sí, porque float (y double) no son de punto flotante decimal, son de punto flotante binario, por lo que el redondeo a posiciones decimales no tiene sentido. Sin embargo, puede redondear la salida.

    – Pavel Minaev

    27 de agosto de 2009 a las 21:49

  • No es sin sentido; es inexacto Hay bastante diferencia.

    – Brooks Moisés

    28 de agosto de 2009 a las 3:08

  • ¿Qué tipo de redondeo esperas? ¿Mitad arriba o redondeo al par más cercano?

    – Buscador de la verdad Rangwan

    3 de agosto de 2014 a las 13:21

avatar de usuario
Dale Hagglund

Si solo desea redondear el número con fines de salida, entonces el "%.2f" cadena de formato es de hecho la respuesta correcta. Sin embargo, si realmente desea redondear el valor de coma flotante para realizar más cálculos, 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 */

Tenga en cuenta que hay tres reglas de redondeo diferentes que puede elegir: redondear hacia abajo (es decir, truncar después de dos lugares 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 punto flotante, estos valores redondeados pueden no ser exactamente los valores decimales “obvios”, pero estarán muy, muy cerca.

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

  • ¿Se puede modificar para admitir el redondeo a una precisión arbitraria?

    usuario472308

    14 mayo 2014 a las 20:12


  • @slater Cuando dice ‘precisión arbitraria’, ¿pregunta sobre el redondeo a, por ejemplo, tres en lugar de dos lugares 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 cualquier biblioteca de precisión múltiple que esté usando.

    – Dale Hagglund

    15 de mayo de 2014 a las 7:09

  • @DaleHagglung El primero, gracias. ¿El ajuste es reemplazar 100 con pow(10, (int)desiredPrecision)?

    usuario472308

    15 mayo 2014 a las 15:09

  • Sí. Para redondear después de k lugares 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 redondo a entero. Entonces, ¿cómo mueves los tres primeros lugares decimales para que queden a la izquierda del punto decimal? Espero que quede claro que multiplicas por 10^3. Ahora puede redondear ese valor a un número entero. Luego, vuelve a colocar los tres dígitos de orden inferior dividiendo por 10 ^ 3.

    – Dale Hagglund

    16 de mayo de 2014 a las 3:19

  • ¿Puedo hacer que esto funcione con doubles también de alguna manera? No parece hacer el trabajo que quiero 🙁 (usando floor y ceil).

    – Sra. Nadie

    20 de junio de 2014 a las 12:40


avatar de usuario
andres coleson

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

Ejemplo:

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

Producción:

37.77

  • Esta forma es mejor porque no hay pérdida de precisión.

    – alberto

    1 de febrero de 2014 a las 18:45

  • @albert Esto también tiene la ventaja de no perder float rango como val * 100 podría desbordarse.

    – chux – Reincorporar a Monica

    3 de agosto de 2014 a las 15:52

avatar de usuario
marca amery

Utilice siempre el printf familia de funciones para esto. Incluso si desea obtener el valor como un valor flotante, es mejor que use snprintf para 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 muestra la respuesta más votada actualmente y varias otras aquí (multiplicar por 100, redondear al número entero más cercano y luego dividir por 100 nuevamente) es defectuoso de dos maneras:

  • Para algunos valores, redondeará en la dirección incorrecta porque la multiplicación por 100 cambia el dígito decimal que determina la dirección del 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 da ida y vuelta, 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 este resultado:

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

Tenga en cuenta que el valor con el que empezamos era inferior a 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. Utilizando snprintf nos da ese resultado, pero usando round(100 * x) / 100 nos da 0.02, lo cual es incorrecto. ¿Por qué? Porque 100 * x nos 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 es incorrecto debido a * 100 y / 100 no siendo realmente inversos 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 fraccionaria; es un valor entero, simplemente 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 snprintf El método devuelve el resultado correcto nuevamente, pero el enfoque de multiplicar luego redondear y luego dividir falla. Eso es porque el valor matemáticamente correcto de 8631192423766613.0 * 100, 863119242376661300.0, no es exactamente representable como un doble; el valor más cercano es 863119242376661248.0. Cuando divides eso entre 100, obtienes 8631192423766612.0 – un número diferente al que empezaste.

Esperemos que sea una demostración suficiente de que usar roundf para redondear a un número de lugares decimales está roto, y que debe usar snprintf en cambio. Si eso te parece un truco horrible, tal vez te tranquilice saber que es básicamente lo que hace CPython.

  • +1 para un ejemplo concreto de lo que sale mal con mi respuesta y otras similares, gracias a la rareza del punto flotante IEEE, y proporciona una alternativa sencilla. Fui consciente de forma periférica, hace bastante tiempo, del gran esfuerzo puesto en la impresión y amigos para mí seguros para valores de coma flotante de ida y vuelta. Supongo que el trabajo realizado entonces podría estar apareciendo aquí.

    – Dale Hagglund

    27 de agosto de 2019 a las 3:26

  • Ejem… Lo siento por la palabra ensalada cerca del final, que ahora es demasiado tarde para editar. Lo que quise decir fue “… mucho esfuerzo puesto en printf y amigos para que estén a salvo…”

    – Dale Hagglund

    1 de septiembre de 2019 a las 0:14

En C++ (o en C con moldes 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 no necesitas crear las 5 variables en esa función, pero las dejo ahí para que puedas 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 lugar decimal según sea necesario.

Qué tal esto:

float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);

  • -1: a) esto no funcionará para números negativos (bueno, el ejemplo es positivo pero aún así). b) no menciona que es imposible almacenar el valor decimal exacto en el flotador

    – John Carter

    28 de agosto de 2009 a las 8:28

  • @therefromhere: (a) Tienes razón (b) ¿Qué es esto? ¿Un examen de secundaria?

    – Daniil

    28 de agosto de 2009 a las 13:20

  • ¿Por qué agregaste 0.5?

    – muhammad tayyab

    3 de junio de 2016 a las 1:48

  • Es necesario seguir las reglas de redondeo.

    – Daniil

    08/06/2016 a las 20:51

  • reglas de redondeo en el contexto del comentario de @Daniil son redondear al más cercano

    – Shmil el gato

    15 mayo 2017 a las 15:43


avatar de usuario
Privacidad Zyp

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

  • -1: a) esto no funcionará para números negativos (bueno, el ejemplo es positivo pero aún así). b) no menciona que es imposible almacenar el valor decimal exacto en el flotador

    – John Carter

    28 de agosto de 2009 a las 8:28

  • @therefromhere: (a) Tienes razón (b) ¿Qué es esto? ¿Un examen de secundaria?

    – Daniil

    28 de agosto de 2009 a las 13:20

  • ¿Por qué agregaste 0.5?

    – muhammad tayyab

    3 de junio de 2016 a las 1:48

  • Es necesario seguir las reglas de redondeo.

    – Daniil

    08/06/2016 a las 20:51

  • reglas de redondeo en el contexto del comentario de @Daniil son redondear al más cercano

    – Shmil el gato

    15 mayo 2017 a las 15:43


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 los sistemas reales, es posible que deba lidiar con varios problemas que pueden resultar en un redondeo que puede no ser lo que esperaba. Por ejemplo, puede estar realizando cálculos financieros en los que los resultados finales se redondean y se muestran a los usuarios con 2 decimales; estos mismos valores se almacenan con precisión fija en una base de datos que puede incluir más de 2 lugares decimales (por varias razones; no hay un número óptimo de lugares para guardar… depende de situaciones específicas que debe admitir cada sistema, por ejemplo, artículos pequeños cuyos precios son fracciones de un centavo por unidad); y cálculos de punto 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 diré 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:

Supongamos que 6 lugares decimales se consideran precisión suficiente para los cálculos en 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.825el resultado es 6.83 como se esperaba.

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

Para val = 6.8249999el resultado es 6.83. Siendo el séptimo lugar decimal 9 en este caso provoca la Round(val,6) función para dar el resultado esperado. Para este caso, podría haber cualquier número de seguimiento 9s.

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

Finalmente, el ejemplo de la pregunta…val = 37.777779 da 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 debe mantenerse para todos los cálculos intermedios en flotantes/dobles. Esto también funciona con valores negativos. No sé si este enfoque es matemáticamente correcto para todas las posibilidades.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad