Saludos, estoy intentando hacer que se me guarden unos decimales concretos para hacer comparaciones despues. Sería posible tener un decimal con, por ejemplo 8 decimales?
Gracias!
No puedes tomar un numero concreto de decimales pero puedes guardarlo de forma normal y luego a la hora de mostrarlo puedes mostrarlo con los decimales que tu quieras independientemente de los que tenga solo tienes que poner en el printf %numdecimales.f y listo ademas a la hora de compararlos no hay problema dado que si un numero tiene tres decimales y el otro cuatro, los numeros que van despues son todo 0 es decir si tienes a = 2.3 y b = 3.123 a la hora de mostrarlos si no pones lo que te he dicho antes, quedaria a = 2.3000000000 y b = 3.12300000000 asi que para compararlos no hay problema, lo unico donde puede haber "problema" es al mostrarlo, eso si, las variables donde metas los números ha de ser float
Un saludo
¿Sería posible tener un decimal con, por ejemplo 8 decimales?
No sería posible con los tipos incorporados del lenguaje C, necesitas un tipo de coma fija, y C ofrece tipos enteros (short
, int
, long
, ...) y de coma flotante (float
, double
, long double
).
Pero a pesar de que C no ofrezca tipos de coma fija, puedes desarrollar aritmética en coma fija con las herramientas ya existentes en el lenguaje, de ser así una operación cualquiera requeriría:
Una posible aproximación usaría operadores de desplazamiento de bits (<<
y >>
) ya que son un equivalente a multiplicar por potencias de 2 (<<
) o dividir por potencias de 2 (>>
).
En tu caso quieres 8 decimales, un valor de 8 dígitos decimales (810) puede contener números del 0 al 99.999.999 (esto son 27 bits1) así que nos quedarían 4 bits para la parte entera:
+--------+-----------------------------+
| entero | decimal |
+--------v-----------------------------+
| 0000.0000000000000000000000000000 |
+--------^-----------------------------+
Lo que nos permite tener números del 0 al 16 (24) con 8 posiciones decimales.
Así que, para sumar 1 y 1 en coma fija haríamos lo siguiente:
int valor = (1 << 27) + (1 << 27); // 1 + 1 = 2
Las operaciones de suma y resta requerirán desplazar los bits (multiplicar) para colocar la parte entera en el lugar adecuado, pero las operaciones de multiplicación y división se pueden hacer directamente:
int valor = (1 << 27) + (1 << 27); // 1 + 1 = 2
valor /= 3; // 2 / 3 = 0.66666666
Comprobémoslo:
int valor = (1 << 27) + (1 << 27);
printf("%d.%d\n", valor >> 27, valor & ((1 << 27) - 1));
valor /= 3;
printf("%d.%d\n", valor >> 27, valor & ((1 << 27) - 1));
Esto debería mostrarnos 2.0
y 0.66666666
pero muestra:
2.0 0.89478485
Esto se debe a que la parte decimal es un porcentaje de una unidad entera, así que deberíamos normalizarlo:
int valor = (1 << 27) + (1 << 27);
printf("%d.%f\n", valor >> 27, (valor & ((1 << 27) - 1)) / (double)(1 << 27));
valor /= 3;
printf("%d.%f\n", valor >> 27, (valor & ((1 << 27) - 1)) / (double)(1 << 27));
Esto produce una salida correcta pero mal formateada:
2.0.000000 0.0.666667
Así que, para facilitar las cosas y trabajar más cómodamente, usemos unas macros y una función auxiliar:
#define SHIFT 27
#define FRACTION_MASK ((1 << SHIFT) - 1)
void print_punto_fijo(int valor)
{
double e = valor >> SHIFT;
double d = (valor & FRACTION_MASK);
double n = e + (d / (double)(1 << SHIFT));
printf("%.8f\n", n);
}
int main(void)
{
int valor = (1 << SHIFT) + (1 << SHIFT);
print_punto_fijo(valor);
print_punto_fijo(valor / 3);
return 0;
}
Produciendo la siguiente salida:
2.00000000 0.66666666
Pero a no ser que quieras desarrollar la aritmética de coma fija por tu cuenta, te aconsejo usar alguna librería de coma fija ya existente:
199.999.99910 = 101.111.101.011.110.000.011.111.1112
A ver...
Hay dos grupos de tipos "nativos", los que representan enteros (int
, long
y asociados) y los que representan números en punto flotante.
El problema de los enteros es que no representan números fraccionales; el problema de los números en punto flotante es que no representan exactamente números decimales en formato decimal. Por ejemplo, 0.5 tiene una expresión finita en binario que cabe en cualquier float, pero 0.1 necesita un número infinito de bits para ser representado en binario1. Mira esta pregunta para más detalles, o busca información sobre el formato IEEE754
¿La solución?
Usar punto flotante y aceptar que puede haber variaciones. Si vas a hacer un cálculo con números de 8 decimales y al final el resultado lo redondeas a 2 decimales, para muchas aplicaciones no importará.
Usar un entero y "convertir los números" para operar con entero. Por ejemplo, si trabajas con precios, hacer que la variable precio
trabaje con céntimos (así, para un producto de 9,99 €, el valor de precio
sería 999
). Por supuesto, hay que acordarse de hacer la conversión a la escala correcta al mostrar los resultados. Naturalmente, tienes que escoger bien el tipo para que sirva para el rango de valores (no puedes usar un int
si trabajas con 20 decimales y la parte entera tiene 10 cifras).
Crear tu propio tipo. Un ejemplo muy burdo sería un struct
con un entero para cada posición decimal. El problema es que a) es más lento que los tipos nativos y b) es más trabajo ya que tienes que crear los operadores.
En todo caso posiblemente haya por allí librerías gratuitas que implementen tipos con decimales arbitrarios.
En general, si la precisión es importante, yo optaría por la segunda opción.