Tengo que calcular algunas variables de punto flotante y mi colega me sugiere que lo use en BigDecimal
lugar de hacerlo, double
ya que será más preciso. ¿Pero quiero saber qué es y cómo aprovecharlo al máximo BigDecimal
?
Tengo que calcular algunas variables de punto flotante y mi colega me sugiere que lo use en BigDecimal
lugar de hacerlo, double
ya que será más preciso. ¿Pero quiero saber qué es y cómo aprovecharlo al máximo BigDecimal
?
Respuestas:
A BigDecimal
es una forma exacta de representar números. A Double
tiene una cierta precisión. Trabajar con dobles de varias magnitudes (digamos d1=1000.0
y d2=0.001
) podría provocar que 0.001
se caiga por completo al sumar, ya que la diferencia de magnitud es tan grande. Con BigDecimal
esto no sucedería.
La desventaja de BigDecimal
es que es más lento, y es un poco más difícil de algoritmos del programa de esa manera (debido a +
-
*
, y /
no se sobrecargue).
Si está tratando con dinero, o la precisión es imprescindible, úsela BigDecimal
. De lo contrario, Doubles
tienden a ser lo suficientemente buenos.
Yo recomiendo la lectura del javadoc de BigDecimal
como lo hacen explicar las cosas mejor que yo aquí :)
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
BigDecimal
", un Doble tendría más "precisión" (más dígitos).
Mi inglés no es bueno, así que escribiré un ejemplo simple aquí.
double a = 0.02;
double b = 0.03;
double c = b - a;
System.out.println(c);
BigDecimal _a = new BigDecimal("0.02");
BigDecimal _b = new BigDecimal("0.03");
BigDecimal _c = _b.subtract(_a);
System.out.println(_c);
Salida del programa:
0.009999999999999998
0.01
¿Alguien todavía quiere usar el doble? ;)
System.out.println(0.003f - 0.002f);
BigDecimal es exacto:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Hay dos diferencias principales con respecto al doble:
La razón por la que debe usar BigDecimal para los cálculos monetarios no es que pueda representar cualquier número, sino que puede representar todos los números que pueden representarse en noción decimal y que incluyen prácticamente todos los números en el mundo monetario (nunca transfiere 1/3 $ a alguien).
Si escribe un valor fraccionario como un valor 1 / 7
decimal, obtendrá
1/7 = 0.142857142857142857142857142857142857142857...
con una secuencia infinita de 142857
. Como solo puede escribir un número finito de dígitos, inevitablemente introducirá un error de redondeo (o truncamiento).
Los números similares 1/10
o 1/100
expresados como números binarios con una parte fraccionaria también tienen un número infinito de dígitos después del punto decimal:
1/10 = binary 0.0001100110011001100110011001100110...
Doubles
almacenar valores como binarios y, por lo tanto, podría introducir un error únicamente al convertir un número decimal en un número binario, sin siquiera hacer ninguna aritmética.
Los números decimales (como BigDecimal
), por otro lado, almacenan cada dígito decimal tal como está. Esto significa que un tipo decimal no es más preciso que un punto flotante binario o un tipo de punto fijo en un sentido general (es decir, no puede almacenar1/7
sin pérdida de precisión), pero es más preciso para números que tienen un número finito de dígitos decimales como A menudo es el caso de los cálculos de dinero.
Java BigDecimal
tiene la ventaja adicional de que puede tener un número arbitrario (pero finito) de dígitos en ambos lados del punto decimal, limitado solo por la memoria disponible.
BigDecimal es la biblioteca numérica de precisión arbitraria de Oracle. BigDecimal es parte del lenguaje Java y es útil para una variedad de aplicaciones que van desde lo financiero a lo científico (ahí es donde estoy).
No hay nada de malo en usar dobles para ciertos cálculos. Sin embargo, suponga que desea calcular Math.Pi * Math.Pi / 6, es decir, el valor de la función Riemann Zeta para un argumento real de dos (un proyecto en el que estoy trabajando actualmente). La división de punto flotante le presenta un doloroso problema de error de redondeo.
BigDecimal, por otro lado, incluye muchas opciones para calcular expresiones con precisión arbitraria. Los métodos de agregar, multiplicar y dividir como se describe en la documentación de Oracle a continuación "toman el lugar" de +, * y / en BigDecimal Java World:
http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html
El método compareTo es especialmente útil en while y para bucles.
Sin embargo, tenga cuidado al usar constructores para BigDecimal. El constructor de cadenas es muy útil en muchos casos. Por ejemplo, el código
BigDecimal onethird = nuevo BigDecimal ("0.33333333333");
utiliza una representación de cadena de 1/3 para representar ese número que se repite infinitamente con un grado específico de precisión. Es muy probable que el error de redondeo esté en algún lugar tan profundo dentro de la JVM que los errores de redondeo no perturben la mayoría de sus cálculos prácticos. Sin embargo, por experiencia personal, he visto un avance gradual. El método setScale es importante a este respecto, como se puede ver en la documentación de Oracle.
/* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
Si está tratando con el cálculo, existen leyes sobre cómo debe calcular y qué precisión debe usar. Si fallas, harás algo ilegal. La única razón real es que la representación en bits de los casos decimales no es precisa. Como dijo Basilio, la mejor explicación es un ejemplo. Solo para complementar su ejemplo, esto es lo que sucede:
static void theDoubleProblem1() {
double d1 = 0.3;
double d2 = 0.2;
System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));
float f1 = 0.3f;
float f2 = 0.2f;
System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));
BigDecimal bd1 = new BigDecimal("0.3");
BigDecimal bd2 = new BigDecimal("0.2");
System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
}
Salida:
Double: 0,3 - 0,2 = 0.09999999999999998
Float: 0,3 - 0,2 = 0.10000001
BigDec: 0,3 - 0,2 = 0.1
También tenemos que:
static void theDoubleProblem2() {
double d1 = 10;
double d2 = 3;
System.out.println("Double:\t 10 / 3 = " + (d1 / d2));
float f1 = 10f;
float f2 = 3f;
System.out.println("Float:\t 10 / 3 = " + (f1 / f2));
// Exception!
BigDecimal bd3 = new BigDecimal("10");
BigDecimal bd4 = new BigDecimal("3");
System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
}
Nos da la salida:
Double: 10 / 3 = 3.3333333333333335
Float: 10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion
Pero:
static void theDoubleProblem2() {
BigDecimal bd3 = new BigDecimal("10");
BigDecimal bd4 = new BigDecimal("3");
System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
}
Tiene la salida:
BigDec: 10 / 3 = 3.3333