zoukankan      html  css  js  c++  java
  • 由BigDecimal类型的数据引出的问题分析

    由BigDecimal类型的数据引出的问题分析

    问题描述:程序中需要判断一个字段是否为0(字段类型为BigDecimal),想都没想,对象的判断用equals?结果却与预期有一定的差距,看下面代码及运行结果。

    	public static void main(String[] args) {
    		BigDecimal decimal1 = BigDecimal.valueOf(0);
    		BigDecimal decimal2 = new BigDecimal("0.00");
    		System.out.println("the result is " +decimal1.equals(decimal2));
    	}
    

    运行结果:

    the result is false
    

    结论: BigDecimal类型比较相等不能简单的通过equals方法实现

    BigDecimal类的equals方法源码如下:

        public boolean equals(Object x) {
            if (!(x instanceof BigDecimal))
                return false;
            BigDecimal xDec = (BigDecimal) x;
            if (x == this)
                return true;
            if (scale != xDec.scale)//这里会比较数字的精度
                return false;
            long s = this.intCompact;
            long xs = xDec.intCompact;
            if (s != INFLATED) {
                if (xs == INFLATED)
                    xs = compactValFor(xDec.intVal);
                return xs == s;
            } else if (xs != INFLATED)
                return xs == compactValFor(this.intVal);
    
            return this.inflate().equals(xDec.inflate());
        }
    

    看上面的注释可以知道,BigDecimal类的equals方法会判断数字的精度,看下面的代码及运行结果:

    	public static void main(String[] args) {
    		BigDecimal decimal1 = BigDecimal.valueOf(0).setScale(2);
    		BigDecimal decimal2 = new BigDecimal("0.00").setScale(2);
    		System.out.println("the result is " +decimal1.equals(decimal2));
    	}
    

    运行结果:

    the result is true
    

    结论: 使用BigDecimal类equals方法判断两个BigDecimal类型的数据时,需要设置精度,否则结果可能不正确。

    adult-blur-caucasian-941555

    思考:每次都设置精度比较麻烦,有其他方式进行相等的比较吗?

    看了下BigDecimal的方法列表,有一个名为compareTo的方法,通过注释可知,貌似可以进行不同精度的比较,看下面的代码。

    	public static void main(String[] args) {
    		BigDecimal decimal1 = BigDecimal.valueOf(1.1);
    		BigDecimal decimal2 = new BigDecimal("1.10");
    		System.out.println("the result is " +decimal1.compareTo(decimal2));
    	}
    

    运行结果:

    the result is 0
    

    0表示两个数相等,所有可以通过compareTo实现不同精度的两个BigDecimal类型的数字是否相等的比较

    引出的问题:公司的项目中,为了避免由于精度丢失引起问题,凡是有精度要求的字段用的都是BigDecimal类型。数据持久层用的是Mybatis框架,Mybatis的mapper文件中有些条件判断用的是BigDecimal对应的字段,如下:

    <select id="selectByCondition" resultType="com.scove.demo.domain.Score">
    	select * from tb_score where 1=1 
    	<if test="score!=null and score!=0">
    		and score&gt;#{score}
    	</if>
    	...
    

    score是一个BigDecimal类型的字段,score!=0 Mybatis是如何进行判断的,会不会用的是上面的equals方法?如果是那么项目上线会不会捅大篓子,想到这儿,有点怕了。写了个程序测了下,这样写完全没问题,能够达到我想要的目的。但是还是有点担心,看看Mybatis底层是如何实现的吧,以免以后犯类似的错误嘛。

    经过分析调试,很快就找到了关键代码的位置,如下:

    public class ExpressionEvaluator {
    
      public boolean evaluateBoolean(String expression, Object parameterObject) {
        Object value = OgnlCache.getValue(expression, parameterObject);
        if (value instanceof Boolean) {
          return (Boolean) value;
        }
        if (value instanceof Number) {
            return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);
        }
        return value != null;
      }
    ...
    
    public final class OgnlCache {
    
    ....
      public static Object getValue(String expression, Object root) {
        try {
          Map<Object, OgnlClassResolver> context = Ognl.createDefaultContext(root, new OgnlClassResolver());
          return Ognl.getValue(parseExpression(expression), context, root);
        } catch (OgnlException e) {
          throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
        }
      }
    

    用的是表达式求值,Ognl这个类的竟然没有源码,apache的官网上找了下,是有相应的源码的,只是需要单独下载,真麻烦,算了不看了。据说底层用的是Spring 的ognl表达式,我也不 关心了。但是以后如果不确定mapper中的test是否正确咋个办?

    想了下,还是写一个工具类在拿不准的时候用一下吧,反正拿不准的时候肯定很少,所以随便写了简单的吧,如下:

    	public static void main(String[] args) {
    		ExpressionEvaluator evaluator = new ExpressionEvaluator();
    		String expression = "score!=null and score!=0";
    		DynamicContext context = new DynamicContext(new Configuration(), null);
    		context.bind("score", BigDecimal.valueOf(0.1));
    		Boolean flag = evaluator.evaluateBoolean(expression , context.getBindings());
    		System.out.println("the result is " +flag);
    	}
    

    运行结果:

    the result is true
    
    总结

    开发过程中,一定要细心去处理细节上的东西,不然一不小心就跳坑里了,轻则系统造成数据的不一致,修复数据;重则造成重大的损失....

  • 相关阅读:
    ubuntu下怎么配置/查看串口-minicom工具
    jpg与pgm(P5)的互相转换(Python)
    hyper-v安装ubuntu18的全过程+踩过的坑(win10家庭版)
    zerotier的下载、安装、配置与使用(win10、ubuntu)
    github page+jekyll构建博客的解决方案
    opencv2.4.13.7的resize函数使用(c++)
    c++中的const和volatile知识自我总结
    各种优化算法详解
    P与NP问题
    vs2017配置pthread.h的方法
  • 原文地址:https://www.cnblogs.com/falco/p/9500833.html
Copyright © 2011-2022 走看看