& # 8220; nuevo BigDecimal (13.3D) & # 8221; resulta impreciso & # 8220; 13.3000000000000007105 .. & # 8221 ;?
-
19-08-2019 - |
Pregunta
¿Cómo es que el BigDecimal
de Java puede ser tan doloroso?
Double d = 13.3D;
BigDecimal bd1 = new BigDecimal(d);
BigDecimal bd2 = new BigDecimal(String.valueOf(d));
System.out.println("RESULT 1: "+bd1.toString());
System.out.println("RESULT 2: "+bd2.toString());
RESULT 1: 13.300000000000000710542735760100185871124267578125
RESULT 2: 13.3
¿Hay alguna situación en la que se desearía el Resultado 1? Sé que Java 1.5 cambió el método toString ()
, pero ¿fue esta la consecuencia prevista?
También me doy cuenta de que BigDecimal
tiene doubleValue ()
, etc., pero la biblioteca con la que estoy trabajando utiliza un toString ()
y No puedo cambiar eso :-(
Saludos.
Solución
Bueno, la API aborda esta aparente inconsistencia en el constructor BigDecimal (double val)
:
Los resultados de este constructor pueden ser algo impredecibles. Uno podría asumir que escribir nuevo BigDecimal (0.1) en Java crea un BigDecimal que es exactamente igual a 0.1 (un valor sin escala de 1, con una escala de 1), pero en realidad es igual a 0.1000000000000000055511151231257827021181583404541015625. Esto se debe a que 0.1 no puede ser representado exactamente como un doble (o, para el caso, como una fracción binaria de cualquier longitud finita). Por lo tanto, el valor que se pasa a la constructor no es exactamente igual a 0.1, a pesar de las apariencias.
El constructor de cadenas, por otro lado, es perfectamente predecible: escribir un nuevo BigDecimal (" 0.1 ") crea un BigDecimal que es exactamente igual a 0.1, como cabría esperar. Por lo tanto, generalmente se recomienda que El constructor de cadenas se utilizará en preferencia a este.
Cuando se debe usar un doble como fuente para un BigDecimal , tenga en cuenta que este constructor proporciona un exacto conversión; no da lo mismo resultado como convertir el doble a un Cadena usando el Método Double.toString (doble) y luego usando BigDecimal (String) constructor. Para obtener ese resultado, usa El valor estático del método (doble) .
Moraleja de la historia: el dolor parece autoinfligido, solo use new BigDecimal (String val)
o BigDecimal.valueOf (double val)
en su lugar =)
Otros consejos
Su problema no tiene nada que ver con BigDecimal
, y todo con Double
, que no puede representar 13.3 con precisión, ya que utiliza fracciones binarias internamente.
Entonces su error se introduce en la primera línea. El primer BigDecimal
simplemente lo conserva, mientras que String.valueOf ()
realiza un redondeo sospechoso que hace que el segundo tenga el contenido deseado, casi por suerte.
Es posible que desee informarse sobre cómo se implementan los valores de coma flotante ( IEEE 754-1985 ). Y de repente, todo se volverá cristalino.
Esto no es culpa de BigDecimal
: es culpa de double
. BigDecimal
representa con precisión el valor exacto de d
. String.valueOf
solo muestra el resultado con unos pocos decimales.
Las fracciones representadas con tipos de números binarios (es decir, double
, float
) no se pueden almacenar con precisión en esos tipos.
Double d = 13.3;
BigDecimal bdNotOk = new BigDecimal(d);
System.out.println("not ok: " + bdNotOk.toString());
BigDecimal bdNotOk2 = new BigDecimal(13.3);
System.out.println("not ok2: " + bdNotOk2.toString());
double x = 13.3;
BigDecimal ok = BigDecimal.valueOf(x);
System.out.println("ok: " + ok.toString());
double y = 13.3;
// pretty lame, constructor's behavior is different from valueOf static method
BigDecimal bdNotOk3 = new BigDecimal(y);
System.out.println("not ok3: " + bdNotOk3.toString());
BigDecimal ok2 = new BigDecimal("13.3");
System.out.println("ok2: " + ok2.toString());
Double e = 0.0;
for(int i = 0; i < 10; ++i) e = e + 0.1; // some fractions cannot be accurately represented with binary
System.out.println("not ok4: " + e.toString()); // should be 1
BigDecimal notOk5 = BigDecimal.valueOf(e);
System.out.println("not ok5: " + notOk5.toString()); // should be 1
/*
* here are some fractions that can be represented exactly in binary:
* 0.5 = 0.1 = 1 / 2
* 0.25 = 0.01 = 1 / 4
* 0.75 = 0.11 = 3 / 4
* 0.125 = 0.001 = 1 / 8
*/
salida:
not ok: 13.300000000000000710542735760100185871124267578125
not ok2: 13.300000000000000710542735760100185871124267578125
ok: 13.3
not ok3: 13.300000000000000710542735760100185871124267578125
ok2: 13.3
not ok4: 0.9999999999999999
not ok5: 0.9999999999999999
Simplemente use BigDecimal.valueOf(d)
o nuevos BigDecimal (s)
.