Entrevista: ¿BigDecimal definitivamente no pierde precisión?

Prefacio

Todos sabemos que las variables de punto flotante perderán precisión al calcular. El siguiente fragmento de código:

System.out.println (0.05 + 0.01); 
System.out.println (1.0 - 0.42); 
System.out.println (4.015 * 100); 
System.out.println (123.3 / 100);

输出 :
0.060000000000000005 
0.5800000000000001 
401.49999999999994 
1.2329999999999999

Se puede ver que al realizar aritmética de punto flotante en Java, ocurrirá el problema de perder precisión. Entonces, si estamos calculando los precios de las materias primas, surgirán problemas. Es muy probable que tengamos 0,06 yuanes en nuestras manos, pero no podemos comprar un producto de 0,05 yuanes y 0,01 yuanes. Porque como se muestra arriba, la suma de los dos es 0.060000000000000005. Este es sin duda un problema muy serio, especialmente cuando aumenta la concurrencia de sitios web de comercio electrónico, los problemas serán enormes. Puede resultar en una falla al realizar un pedido o problemas en la reconciliación. Entonces, a continuación, podemos usar la clase BigDecimal en Java para resolver este tipo de problema.

Popularizar:

La precisión de float en Java es de 6-7 dígitos significativos. La precisión de double es de 15 a 16 dígitos.

API

Constructor:

Constructor Descripción                       
  BigDecimal (int) Crea un objeto con el valor entero especificado por el parámetro.      
  BigDecimal (doble) crea un objeto con el valor de doble precisión especificado por el parámetro.     
  BigDecimal (long) crea un objeto con el valor entero largo especificado por el parámetro.     
  BigDecimal (String) crea un objeto con el valor representado por la cadena especificada por el parámetro.


función:

 Método Descripción                          
  add (BigDecimal) Agrega los valores en el objeto BigDecimal y luego devuelve este objeto. 
  restar (BigDecimal) Restar el valor en el objeto BigDecimal y luego devolver este objeto. 
  multiplicar (BigDecimal) Multiplica los valores en el objeto BigDecimal y luego devuelve este objeto. 
  dividir (BigDecimal) Divide el valor en el objeto BigDecimal y luego devuelve este objeto. 
  toString () Convierte el valor numérico del objeto BigDecimal en una cadena.    
  doubleValue () devuelve el valor del objeto BigDecimal como un número de precisión doble.   
  floatValue () devuelve el valor en el objeto BigDecimal como un número de precisión simple.   
  longValue () devuelve el valor del objeto BigDecimal como un entero largo.    
  intValue () devuelve el valor en el objeto BigDecimal como un número entero.


Debido a los tipos numéricos generales, por ejemplo, el doble no puede representar con precisión números con más de 16 dígitos.

La precisión BigDecimal también se pierde

Cuando usamos BigDecimal, tiene sentido usar su constructor BigDecimal (String) para crear objetos. Otros, como BigDecimal b = new BigDecimal (1), todavía tienen el problema de la pérdida de precisión. El siguiente código:

BigDecimal a = nuevo BigDecimal (1.01); 
BigDecimal b = nuevo BigDecimal (1.02); 
BigDecimal c = new BigDecimal ("1.01"); 
BigDecimal d = new BigDecimal ("1.02"); 
System.out.println (a.add (b)); 
System.out.println (c.add (d));

输出 :
2.0300000000000000266453525910037569701671600341796875 
2.03


Se puede observar que la pérdida de precisión de BigDecimal es aún más excesiva. Sin embargo, cuando se utilizan las variables constructoras BigDecimal (String) de BigDecimal, no existe tal problema. La razón está contenida en los principios de composición de computadoras, y su codificación determina este resultado. Long puede almacenar con precisión 19 dígitos, mientras que doble solo puede almacenar 16 dígitos. Debido a que double tiene bits exp, puede almacenar más de 16 dígitos, pero debe ser a costa de una inexactitud de orden inferior. Si necesita un almacenamiento preciso de más de 19 dígitos, debe usar BigInteger para guardar, por supuesto, se sacrificará parte del rendimiento. Por tanto, cuando generalmente usamos BigDecimal para resolver el problema de pérdida de precisión en operaciones comerciales, debemos usarlo para construir un constructor cuyo parámetro sea String al declarar un objeto BigDecimal.

Al mismo tiempo, este principio también se menciona en Effective Java and MySQL Must Know Must Know. Float y double solo se pueden utilizar para cálculos científicos y cálculos de ingeniería. Necesitamos utilizar BigDecimal en la informática empresarial.

Y damos instrucciones oficialmente a partir de los comentarios del código fuente. Lo siguiente es parte de las anotaciones en el constructor del parámetro de tipo doble de la clase BigDecimal:

* The results of this constructor can be somewhat unpredictable.
     * One might assume that writing {@code new BigDecimal(0.1)} in
     * Java creates a {@code BigDecimal} which is exactly equal to
     * 0.1 (an unscaled value of 1, with a scale of 1), but it is
     * actually equal to
     * 0.1000000000000000055511151231257827021181583404541015625.
     * This is because 0.1 cannot be represented exactly as a
     * {@code double} (or, for that matter, as a binary fraction of
     * any finite length).  Thus, the value that is being passed
     * <i>in</i> to the constructor is not exactly equal to 0.1,
     * appearances notwithstanding.
       ……
        * When a {@code double} must be used as a source for a
     * {@code BigDecimal}, note that this constructor provides an
     * exact conversion; it does not give the same result as
     * converting the {@code double} to a {@code String} using the
     * {@link Double#toString(double)} method and then using the
     * {@link #BigDecimal(String)} constructor.  To get that result,
     * use the {@code static} {@link #valueOf(double)} method.
     * </ol>
public BigDecimal(double val) {
    this(val,MathContext.UNLIMITED);
}


第一段也说的很清楚它只能计算的无限接近这个数,但是无法精确到这个数。第二段则说,如果要想准确计算这个值,那么需要把double类型的参数转化为String类型的。并且使用BigDecimal(String)这个构造方法进行构造。 去获取结果。

正确运用BigDecimal

另外,BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象,由刚才我们所罗列的API也可看出。

在一般开发过程中,我们数据库中存储的数据都是float和double类型的。在进行拿来拿去运算的时候还需要不断的转化,这样十分的不方便。这里我写了一个工具类:

/**
 * @author: Ji YongGuang.
 * @date: 19:50 2017/12/14.
 */
public class BigDecimalUtil {

    private BigDecimalUtil() {

    }

    public static BigDecimal add(double v1, double v2) {// v1 + v2
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2);
    }

    public static BigDecimal sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2);
    }

    public static BigDecimal mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2);
    }

    public static BigDecimal div(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        // 2 = 保留小数点后两位   ROUND_HALF_UP = 四舍五入
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);// 应对除不尽的情况
    }
}


该工具类提供了double类型的基本的加减乘除运算。直接调用即可。

最后

感谢大家看到这里,文章有不足,欢迎大家指出;如果你觉得写得不错,那就给我一个赞吧。


Supongo que te gusta

Origin blog.51cto.com/14849432/2540536
Recomendado
Clasificación