Introducción y uso de BigDecimal en Java, formato BigDecimal, preguntas frecuentes sobre BigDecimal

1. Descripción general de BigDecimal

BigDecimal es una clase API proporcionada por Java en el paquete java.math 线程安全, que se utiliza para realizar operaciones precisas en números con más de 16 dígitos significativos. La variable de punto flotante de doble precisión puede manejar números significativos de 16 bits, pero en aplicaciones reales, puede ser necesario operar y procesar números más grandes o más pequeños. En circunstancias normales, para aquellos números que no requieren una precisión de cálculo precisa, podemos usar directamente Float y Double para procesar, pero Double.valueOf (String) y Float.valueOf (String) perderán precisión. Por lo tanto, durante el desarrollo, si necesitamos resultados de cálculo precisos, debemos usar la clase BigDecimal para operar.

Lo que BigDecimal crea es un objeto, por lo que no podemos usar operadores aritméticos tradicionales como +, -, *, / para realizar operaciones matemáticas directamente en sus objetos, sino que debemos llamar a sus métodos correspondientes. Los parámetros del método también deben ser objetos BigDecimal. Los constructores son métodos especiales de una clase que se utilizan para crear objetos, especialmente objetos con parámetros.

2. Métodos de construcción comunes de BigDecimal

método significado
GranDecimal(int val) Crea un objeto con el valor entero especificado por el argumento.
BigDecimal(doble valor) Crea un objeto con el valor doble especificado por el argumento.不推荐使用,因为存在精度丢失问题
BigDecimal(valor largo) Crea un objeto con el valor largo especificado por el argumento.
BigDecimal (valor de cadena) Crea un objeto con el valor de cadena especificado por el argumento.推荐使用

3. Métodos comunes de BigDecimal

Nota: Al realizar operaciones en BigDecimal, debe asegurarse de que el objeto en sí no pueda ser nulo; de lo contrario, se generará una excepción de puntero nulo.

método significado
agregar(GranDecimal) Suma los valores en objetos BigDecimal y devuelve un objeto BigDecimal
restar(GranDecimal) Reste los valores en el objeto BigDecimal y devuelva el objeto BigDecimal
multiplicar(GranDecimal) Multiplica los valores en objetos BigDecimal, devolviendo un objeto BigDecimal
dividir(GranDecimal) Divide los valores en el objeto BigDecimal y devuelve el objeto BigDecimal.该方法可能会遇到无限精度问题,会抛出异常,使用时需注意。详细见下方的无限精度的坑
abdominales() Convertir el valor de un objeto BigDecimal a un valor absoluto
valor doble() Convertir el valor de un objeto BigDecimal a doble
valor flotante() Convierta el valor de un objeto BigDecimal en un número de precisión simple
valorlargo() Convertir un valor en un objeto BigDecimal a un entero largo
valorint() Convertir el valor de un objeto BigDecimal a un número entero
comparar con (valor decimal grande) Compare el tamaño y devuelva el tipo int. 0 (igual) 1 (mayor que) -1 (menor que)
Encadenar() Utilice notación científica cuando sea necesario.
toPlainString() No se utilizan exponentes.推荐使用
toEngineeringString() Utilice notación de ingeniería cuando sea necesario. La notación de ingeniería es un método para registrar números que se utiliza a menudo en los cálculos de ingeniería. Es similar al método de ciencia y tecnología, pero requiere que la potencia de 10 sea múltiplo de 3.
max(Valor decimal grande) Compara dos valores y devuelve el valor máximo
negar() Encuentra el número opuesto, lo positivo se vuelve negativo, lo negativo se vuelve positivo
potencia(int n) Encuentre la potencia, por ejemplo, el valor de BigDecimal.valueOf(2).pow(3) es 8

ejemplo de código

import java.math.BigDecimal;

public class Test {
    
    
    public static void main(String[] args){
    
    
        BigDecimal b1 = new BigDecimal("1");
        BigDecimal b2 = new BigDecimal("2");
        BigDecimal b3 = new BigDecimal("4");
        System.out.println("相加:"+b1.add(b2));
        System.out.println("相减:"+b1.subtract(b2));
        System.out.println("相乘:"+b2.multiply(b3));
        System.out.println("相除:"+b2.divide(b3));
    }
}

4. Avance BigDecimal

Ocho modos de redondeo para BigDecimal

El método BigDecimal.setScale() se utiliza para formatear el punto decimal.
setScale(1) significa retener un decimal. De forma predeterminada, el método de redondeo
setScale(1, BigDecimal.ROUND_DOWN) se utiliza para eliminar directamente los decimales sobrantes. Por ejemplo, 2,35 se convertirá en 2,3
setScale(1, BigDecimal.ROUND_UP) redondeo, 2,35 se convertirá en 2,4
setScale(1,BigDecimal.ROUND_HALF_UP) redondeo, 2,35 se convertirá en 2,4
setScaler(1,BigDecimal.ROUND_HALF_DOWN) redondeo, 2,35 se convertirá en 2,3, si es 5 , redondear hacia abajo
setScaler(1,BigDecimal.ROUND_CEILING) está redondeando cerca del infinito positivo
setScaler(1,BigDecimal.ROUND_FLOOR) está redondeando cerca del infinito negativo, los números >0 tienen el mismo efecto que ROUND_UP, los números <0 tienen el mismo efecto que ROUND_DOWN
setScaler(1,BigDecimal.ROUND_HALF_EVEN) Redondear al número más cercano, si la distancia a dos números adyacentes es igual, luego redondear hacia el número par adyacente.

1. ROUND_UP, redondeando desde 0, siempre aumentando el número antes de descartar la parte distinta de cero (siempre suma 1 al número antes de la parte descartada distinta de cero).
Tenga en cuenta que este modo de redondeo nunca reduce el tamaño del valor calculado.
por ejemplo: Mantener 1 decimal 1,60->1,6 1,61->1,7 1,66->1,7, -1,62->-1,7

2. ROUND_DOWN, redondeando en la dirección cercana a 0, nunca suma datos (es decir, truncamiento) antes de descartar una determinada parte, este método solo resta pero no suma.
por ejemplo: Mantener 1 decimal 1,60->1,6 1,61->1,6 1,66->1,6, -1,62->-1,6

3. ROUND_CEILING, redondeando hacia el infinito positivo. Si el valor es positivo, el método de redondeo es consistente con ROUND_UP. Si es negativo, el método de redondeo es consistente con ROUND_DOWN. Este modo nunca reducirá el valor calculado.
por ejemplo: Mantener 1 decimal 1,60->1,6 1,61->1,7 1,66->1,7, -1,62->-1,6

4. ROUND_FLOOR, redondea hacia el infinito negativo, si el valor es positivo el comportamiento de redondeo es el mismo que ROUND_DOWN, si es negativo el comportamiento de redondeo es el mismo que ROUND_UP. Este modo nunca incrementa el valor calculado.
por ejemplo: Mantener 1 decimal 1,60->1,6 1,61->1,6 1,66->1,6, -1,62->-1,7

5. ROUND_HALF_UP, redondeando al número "más cercano", es decir, redondeando.
por ejemplo: Mantener 1 decimal 1,61->1,6 1,65->1,7 1,66->1,7, -1,62->-1,6

6. ROUND_HALF_DOWN, redondea al número "más cercano", si la distancia entre los dos números adyacentes es igual, es el modo de redondeo hacia arriba, es decir, redondeo hacia arriba.
por ejemplo: Mantener 1 decimal 1,61->1,6 1,65->1,6 1,66->1,7, -1,62->-1,6

7. ROUND_HALF_EVEN, redondea al número "más cercano". Si la distancia a dos números adyacentes es igual, redondea al número par adyacente. Si el número a la izquierda de la parte descartada es un número impar, el comportamiento de redondeo es el mismo que ROUND_HALF_UP; si es un número par, el comportamiento de redondeo es el mismo que ROUND_HALF_DOWN.
Tenga en cuenta que este modo de redondeo minimiza los errores acumulativos cuando se repite una serie de cálculos.
Este modo de redondeo también se conoce como "redondeo bancario" y se utiliza principalmente en los Estados Unidos. Hay dos casos de redondeo a cinco puntos. Si el dígito anterior es un número impar se coloca, en caso contrario se descarta. El siguiente ejemplo es el resultado de este método de redondeo conservando 1 decimal.
por ejemplo, 1,15->1,2, 1,25->1,2

8. ROUND_UNNECESSARY, el resultado del cálculo es preciso y no requiere modo de redondeo. Si se especifica este modo de redondeo para una operación que obtiene un resultado exacto, se genera una excepción ArithmeticException.

ejemplo de código

BigDecimal b = new BigDecimal("1.6666");
System.out.println("result b:" + b.setScale(2, BigDecimal.ROUND_HALF_UP)); // 1.67
System.out.println("result b:" + b.setScale(2)); // 精度错误

Resultados de:

result b:1.67
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary

Análisis de causa:

El modo de redondeo predeterminado utilizado por el método setScale es ROUND_UNNECESSARY. No es necesario utilizar el modo de redondeo. La precisión de configuración es de 2 dígitos. Sin embargo, si hay 4 dígitos después del punto decimal, definitivamente se generará una excepción.

Formato BigDecimal, conversión de punto decimal

BigDecimal se puede utilizar en combinación con DecimalFormat para formatear la cantidad, como mantener dos dígitos después del punto decimal, rellenar ceros para menos de dos dígitos y redondear los dos dígitos restantes.

import java.math.BigDecimal;
import java.text.DecimalFormat;

public class Test {
    
    
    public static void main(String[] s){
    
    
        System.out.println(formatToNumber(new BigDecimal("12333.435")));
        System.out.println(formatToNumber(new BigDecimal(0)));
        System.out.println(formatToNumber(new BigDecimal("0.00")));
        System.out.println(formatToNumber(new BigDecimal("0.001")));
        System.out.println(formatToNumber(new BigDecimal("0.006")));
        System.out.println(formatToNumber(new BigDecimal("0.206")));
        System.out.println(formatToNumber(new BigDecimal("1.22")));
    }
    /**
     * @desc
     * @param obj 传入的小数
     * @return
     */
    public static String formatToNumber(BigDecimal obj) {
    
    
        // DecimalFormat默认使用的是进位方式是RoundingMode.HALF_EVEN,此舍入模式也称为“银行家算法”,主要在美国使用。
        //银行家算法:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一
        DecimalFormat df = new DecimalFormat("###,##0.00"); 
        return df.format(obj);
    }
}

Resultados de:

12,333.44
0.00
0.00
0.00
0.01
0.21
1.22

需注意:

  1. El método de redondeo predeterminado de DecimalFormat no es redondear, por lo que cuando es necesario redondear el punto decimal, puede ser diferente de lo esperado. Para obtener más información, consulte " Acerca de los problemas de redondeo de DecimalFormat, los errores del redondeo de DecimalFormat " .
  2. new DecimalFormat("###,##0.00") necesita tener un 0 delante del punto decimal, para que los números entre 0-1 tengan el formato normal; si no hay 0 delante del punto decimal de ##0.00, entonces los números entre 0-1 El número se perderá antes del punto decimal 0, el código es el siguiente:
import java.math.BigDecimal;
import java.text.DecimalFormat;

public class Test {
    
    
    public static void main(String[] s){
    
    
        System.out.println(formatToNumber(new BigDecimal("12333.435")));
        System.out.println(formatToNumber(new BigDecimal(0)));
        System.out.println(formatToNumber(new BigDecimal("0.00")));
        System.out.println(formatToNumber(new BigDecimal("0.001")));
        System.out.println(formatToNumber(new BigDecimal("0.006")));
        System.out.println(formatToNumber(new BigDecimal("0.206")));
        System.out.println(formatToNumber(new BigDecimal("1.22")));
    }
    /**
     * @desc
     * @param obj 传入的小数
     * @return
     */
    public static String formatToNumber(BigDecimal obj) {
    
    
        // DecimalFormat默认使用的是进位方式是RoundingMode.HALF_EVEN,此舍入模式也称为“银行家算法”,主要在美国使用。
        //银行家算法:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一
        DecimalFormat df = new DecimalFormat("###,##.00");
        return df.format(obj);
    }
}

Resultados de:

1,23,33.44
.00
.00
.00
.01
.21
1.22

Formato de moneda y formato de porcentaje

A menudo se ve que el monto se expresa como ¥120,00 y la tasa de interés se expresa como 0,8%. Aquí ampliaremos el formato de moneda y el formato de porcentaje de BigDecimal.

El método format() de la clase NumberFormat puede utilizar un objeto BigDecimal como parámetro. BigDecimal se puede utilizar para formatear valores monetarios, valores porcentuales y valores numéricos generales que excedan los 16 dígitos significativos.

Objeto NumberFormat:
getCompactNumberInstance(); devuelve el formato numérico compacto de la configuración regional predeterminada FORMAT con el estilo de formato "SHORT".
getCurrencyInstance​(Locale inLocale); Devuelve el formato de moneda de la configuración regional especificada. Si no se especifica ningún parámetro, se utiliza el idioma predeterminado como parámetro.
getInstance​(Locale inLocale); Devuelve el formato de número universal de la configuración regional especificada. Si no se especifica ningún parámetro, se utiliza el idioma predeterminado como parámetro.
getPercentInstance​(Locale inLocale); Devuelve el formato de porcentaje de la configuración regional especificada. Si no se especifica ningún parámetro, se utiliza el idioma predeterminado como parámetro.

Ejemplo de código:

import java.math.BigDecimal;
import java.text.NumberFormat;

public class Test {
    
    
    public static void main(String[] args){
    
    
        NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用
        NumberFormat percent = NumberFormat.getPercentInstance();  //建立百分比格式化引用
        percent.setMinimumFractionDigits(2);//设置数的小数部分所允许的最小位数(如果不足后面补0)
        percent.setMaximumFractionDigits(3);//设置数的小数部分所允许的最大位数(如果超过会四舍五入)

        BigDecimal amount = new BigDecimal("250600.42"); //金额
        BigDecimal interestRate = new BigDecimal("0.0004"); //利率
        BigDecimal interest = amount .multiply(interestRate); //相乘

        System.out.println("金额: " + currency.format(loanAmount));
        System.out.println("利率: " + percent.format(interestRate));
        System.out.println("利息: " + currency.format(interest));
    }
}

Resultados de:

金额: ¥250,600.42
利率: 0.04%
利息: ¥100.24

NumberFormat proporciona referencias en múltiples formatos de moneda, como ¥ (RMB), $ (dólares estadounidenses, dólares británicos), etc. Escribiré una publicación de blog para presentar los detalles más adelante.

5. Preguntas frecuentes sobre BigDecimal

Trampa 1: crear un pozo donde se pierde la precisión de BigDecimal

BigDecimal proporciona múltiples métodos de creación, que se pueden crear directamente mediante new o mediante BigDecimal#valueOf. El uso inadecuado de estos dos métodos también puede provocar problemas de precisión. como sigue:

public static void main(String[] args) throws Exception {
    
    
   BigDecimal b1= new BigDecimal(0.1);
   System.out.println(b1);
   BigDecimal b2= BigDecimal.valueOf(0.1);
   System.out.println(b2);
   BigDecimal b3= BigDecimal.valueOf(0.111111111111111111111111111234);
   System.out.println(b3);
}

Resultados de:

0.1000000000000000055511151231257827021181583404541015625
0.1
0.1111111111111111

En el ejemplo anterior, ambos métodos pasaron el parámetro de tipo doble 0.1, pero b1 aún tenía problemas de precisión. La razón de este problema es que la computadora no puede representar con precisión el número 0.1. La precisión ya se pierde cuando se envía a BigDecimal, pero la implementación de BigDecimal#valueOf es completamente diferente. Como se muestra en el código fuente a continuación, BigDecimal#valueOf construye BigDecimal convirtiendo números de punto flotante en cadenas, evitando así problemas.

public static BigDecimal valueOf(double val) {
    
    
   return new BigDecimal(Double.toString(val));
}

en conclusión:

  • Primero, cuando utilice el constructor BigDecimal, intente pasar cadenas en lugar de tipos de punto flotante;
  • En segundo lugar, si no se puede cumplir la primera condición, se puede utilizar el método BigDecimal#valueOf para construir el valor de inicialización.但是valueOf受double类型精度影响,当传入参数小数点后的位数超过double允许的16位精度还是可能会出现问题的

Escollo 2: El peligro de la comparación equivalente

Generalmente, al comparar si dos valores son iguales, se utiliza el método igual. Sin embargo, el uso de iguales en BigDecimal puede provocar resultados incorrectos. BigDecimal proporciona el método compareTo. En muchos casos, es necesario utilizar compareTo para comparar dos valores. Como sigue:

public static void main(String[] args){
    
    
    BigDecimal b1 = new BigDecimal("1.0");
    BigDecimal b2 = new BigDecimal("1.00");
    System.out.println(b1.equals(b2));
    System.out.println(b1.compareTo(b2));
}

Resultados de:

false
0

La razón de este resultado es que igual no solo compara si los valores son iguales, sino que también compara si la precisión es la misma. En el ejemplo, debido a que la precisión de los dos valores es diferente, todos los resultados serán diferentes. Y compareTo solo compara el tamaño de los valores. Los valores devueltos son -1 (menor que), 0 (igual que), 1 (mayor que).

en conclusión

  • Si compara los tamaños de dos valores BigDecimal, utilice el método compareTo implementado;
  • Si la precisión de la comparación es estrictamente limitada, considere utilizar el método de igualdad.

Escollo 3: El peligro de la precisión infinita

BigDecimal no representa precisión infinita. Cuando dos números no se pueden dividir, aparecerán hoyos de precisión infinita, como se muestra a continuación:

 public static void main(String[] args){
    
    
   BigDecimal b1 = new BigDecimal("1.0");
    BigDecimal b2 = new BigDecimal("3.0");
    b1.divide(b2);
}

Resultados de:

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
	at java.math.BigDecimal.divide(BigDecimal.java:1693)
	at com.demo.controller.Test.main(Test.java:29)

La documentación oficial explica esta excepción de la siguiente manera:

Si el cociente tiene una expansión decimal no terminante y se especifica que la operación devuelva un resultado exacto, se genera una excepción ArithmeticException. De lo contrario, se devuelve el resultado exacto de la división, como se hace con otras operaciones.

El significado general es que si durante la operación de división, si el cociente es un decimal infinito (como 0,333...) y se espera que el resultado de la operación sea un número exacto, entonces se generará una ArithmeticException.

En este caso, sólo necesita especificar la precisión del resultado cuando utilice el método de división:

public static void main(String[] args){
    
    
   BigDecimal b1 = new BigDecimal("1.0");
   BigDecimal b2 = new BigDecimal("3.0");
   System.out.println(b1.divide(b2,2, RoundingMode.HALF_UP));//0.33
}

en conclusión:

  • Al realizar (todas) las operaciones con BigDecimal, intente especificar la precisión y el modo de redondeo.

Error 4: errores de salida de tres cadenas de BigDecimal

Al convertir un BigDecimal en una cadena, se pueden producir resultados inesperados. Como sigue:

public static void main(String[] args){
    
    
   BigDecimal bg = new BigDecimal("1E11");
    System.out.println(bg.toString()); // 1E+11
    System.out.println(bg.toPlainString()); // 100000000000
    System.out.println(bg.toEngineeringString()); // 100E+9
}

Resultados de:

1E+11
100000000000
100E+9

Se puede ver que los resultados de salida de los tres métodos pueden ser diferentes. Este puede no ser el resultado esperado. BigDecimal tiene tres métodos para convertir al tipo de cadena correspondiente. Recuerde no usar el incorrecto:

El siguiente contenido presenta las diferencias y el uso de los tres métodos toString en java.math.BigDecimal

toPlainString(): no utiliza ningún exponente.
toString(): utiliza notación científica cuando sea necesario.
toEngineeringString(): utilice notación de ingeniería cuando sea necesario. La notación de ingeniería es un método para registrar números que se utiliza a menudo en los cálculos de ingeniería. Es similar al método de ciencia y tecnología, pero requiere que la potencia de 10 sea múltiplo de 3.

Para conocer las diferencias específicas entre los tres métodos, consulte otra publicación del blog " Las diferencias y el uso de toString(), toPlainString() y toEngineeringString() de BigDecimal " .

Error 5: los parámetros no pueden ser NULL cuando se usa BigDecimal para los cálculos

Cuando utilice el tipo BigDecimal para cálculos, al sumar, restar, multiplicar, dividir o comparar tamaños, debe asegurarse de que los dos valores involucrados en el cálculo no puedan estar vacíos; de lo contrario, se generará una java.lang.NullPointerException.

Ejemplo de código:

BigDecimal b1 = new BigDecimal("1");
BigDecimal b2 = null;
System.out.println("相加:"+b2.add(b1));

resultado:

Exception in thread "main" java.lang.NullPointerException
	at com.demo.controller.Test.main(Test.java:14)

Error 6: el dividendo no puede ser 0 cuando se utiliza BigDecimal para cálculos de división

Ejemplo de código:

BigDecimal b1 = new BigDecimal("1");
BigDecimal b2 = new BigDecimal("0");
System.out.println(b1.divide(b2));

Resultados de:

Exception in thread "main" java.lang.ArithmeticException: Division by zero

Error 7: el orden de ejecución no se puede revertir (la ley conmutativa de la multiplicación no es válida)

Es de sentido común que la multiplicación satisface la ley conmutativa, pero en el mundo de las computadoras puede haber situaciones en las que la ley conmutativa de la multiplicación no se cumple;

Ejemplo de código:

BigDecimal b1 = BigDecimal.valueOf(1.0);
BigDecimal b2 = BigDecimal.valueOf(3.0);
BigDecimal b3 = BigDecimal.valueOf(3.0);
System.out.println(b1.divide(b2, 2, RoundingMode.HALF_UP).multiply(b3)); // 0.990
System.out.println(b1.multiply(b3).divide(b2, 2, RoundingMode.HALF_UP)); // 1.00

Resultados de:

0.990
1.00

Después de intercambiar la orden de ejecución, los resultados producidos pueden ser diferentes, lo que puede causar ciertos problemas, se recomienda multiplicar primero y luego dividir al usar la orden.

Supongo que te gusta

Origin blog.csdn.net/weixin_49114503/article/details/129056256
Recomendado
Clasificación