¿Doble contra BigDecimal?

Respuestas:

446

A BigDecimales una forma exacta de representar números. A Doubletiene una cierta precisión. Trabajar con dobles de varias magnitudes (digamos d1=1000.0y d2=0.001) podría provocar que 0.001se caiga por completo al sumar, ya que la diferencia de magnitud es tan grande. Con BigDecimalesto no sucedería.

La desventaja de BigDecimales 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, Doublestienden a ser lo suficientemente buenos.

Yo recomiendo la lectura del javadoc de BigDecimalcomo lo hacen explicar las cosas mejor que yo aquí :)

extraneon
fuente
Sí, estoy calculando el precio de las acciones, así que creo que BigDecimal es útil en este caso.
Truong Ha
55
@Truong Ha: Al trabajar con precios, desea utilizar BigDecimal. Y si los almacena en la base de datos, quiere algo similar.
extraneon
98
Decir que "BigDecimal es una forma exacta de representar números" es engañoso. 1/3 y 1/7 no se pueden expresar exactamente en un sistema de números de base 10 (BigDecimal) o en un sistema de números de base 2 (flotante o doble). 1/3 podría expresarse exactamente en base 3, base 6, base 9, base 12, etc. y 1/7 podría expresarse exactamente en base 7, base 14, base 21, etc. Las ventajas de BigDecimal son que es una precisión arbitraria y que los humanos están acostumbrados a los errores de redondeo que obtienes en la base 10.
procrastinate_later
3
Un buen punto sobre que es más lento, me ayuda a entender por qué el código del equilibrador de carga de la cinta de Netflix trata con los dobles, y luego tiene líneas como esta:if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
michaelok
@extraneon Creo que quiere decir "si la precisión es imprescindible, use BigDecimal", un Doble tendría más "precisión" (más dígitos).
jspinella
164

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? ;)

Albahaca
fuente
11
@eldjon Eso no es cierto, mira este ejemplo: BigDecimal two = new BigDecimal ("2"); BigDecimal ocho = nuevo BigDecimal ("8"); System.out.println (two.divide (ocho)); Esto imprime 0.25.
Ludvig W
44
Dobla Forevr: D
vach
No obstante, si utiliza un flotador, en su lugar obtendrá la misma precisión que BigDecimal pero un rendimiento mucho mejor
EliuX
3
@EliuX Float puede funcionar con 0.03-0.02, pero otros valores aún son imprecisos: System.out.println(0.003f - 0.002f);BigDecimal es exacto:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin
50

Hay dos diferencias principales con respecto al doble:

  • Precisión arbitraria, de manera similar a BigInteger, pueden contener una cantidad de precisión arbitraria y tamaño
  • Base 10 en lugar de Base 2, un BigDecimal es una escala n * 10 ^ donde n es un entero con signo arbitrario grande y la escala puede considerarse como el número de dígitos para mover el punto decimal hacia la izquierda o hacia la derecha

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).

Meros
fuente
2
Esta respuesta realmente explica la diferencia y la razón de usar BigDecimal sobre el doble. Los problemas de rendimiento son secundarios.
Vortex
Esto no es 100% cierto. Usted escribió que un BigDecimal es "n * 10 ^ scale". Java solo hace eso para números negativos. Tan correcto sería: "unscaledValue × 10 ^ -scale". Para números positivos, el BigDecimal consiste en un "valor sin escala de entero arbitrario de precisión y una escala entera de 32 bits", mientras que la escala es el número de dígitos a la derecha del punto decimal.
la mano de NOD
25

Si escribe un valor fraccionario como un valor 1 / 7decimal, 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/10o 1/100expresados ​​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 BigDecimaltiene 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.

Olivier Jacot-Descombes
fuente
7

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.

pescador
fuente
BigDecimal es parte de la biblioteca numérica de precisión arbitraria de Java . 'In-house' no tiene sentido en este contexto, especialmente como fue escrito por IBM.
Marqués de Lorne
@EJP: busqué en la clase BigDecimal y aprendí que solo una parte de ella está escrita por IBM. Comentario de copyright a continuación: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK
7

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 
jfajunior
fuente