因BigDecimal类型数据引出的问题详析

 更新时间:2018年08月19日 14:23:13   作者:lensar  
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算,下面这篇文章主要给大家介绍了因BigDecimal类型数据引出的问题的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下

前言

我们都知道,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类型的数据时,需要设置精度,否则结果可能不正确。

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

看了下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

总结

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

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • Java基础之SpringBoot整合knife4j

    Java基础之SpringBoot整合knife4j

    Swagger现在已经成了最流行的接口文档生成与管理工具,但是你是否在用的时候也在吐槽,它是真的不好看,接口测试的json数据没法格式化,测试地址如果更改了还要去改配置,接口测试时增加token验证是真的麻烦…针对Swagger的种种缺点,Knife4j就呼之欲出了.需要的朋友可以参考下
    2021-05-05
  • 关于@Component注解下的类无法@Autowired问题

    关于@Component注解下的类无法@Autowired问题

    这篇文章主要介绍了关于@Component注解下的类无法@Autowired问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 实例讲解Java中random.nextInt()与Math.random()的基础用法

    实例讲解Java中random.nextInt()与Math.random()的基础用法

    今天小编就为大家分享一篇关于实例讲解Java中random.nextInt()与Math.random()的基础用法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-02-02
  • Java使用ES Client 调用滚动查询及Elasticsearch滚动查询Scrolling机制

    Java使用ES Client 调用滚动查询及Elasticsearch滚动查询Scrolling机制

    Elasticsearch提供了一种称为"滚动查询"(Scrolling)的机制,用于处理大型数据集的分页查询,这篇文章给大家介绍滚动查询的一般步骤及Java使用ESClient调用滚动查询的方法,感兴趣的朋友一起看看吧
    2023-08-08
  • Sentinel中三种流控模式的使用详解

    Sentinel中三种流控模式的使用详解

    这篇文章主要为大家详细介绍了Sentinel中三种流控模式(预热模式,排队等待模式和热点规则)的使用,文中的示例代码讲解详细,感兴趣的可以了解下
    2023-08-08
  • Schedule定时任务在分布式产生的问题详解

    Schedule定时任务在分布式产生的问题详解

    这篇文章主要介绍了Schedule定时任务在分布式产生的问题详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • Spring整合Mybatis思路梳理总结

    Spring整合Mybatis思路梳理总结

    mybatis-plus是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生,下面这篇文章主要给大家介绍了关于SpringBoot整合Mybatis-plus案例及用法实例的相关资料,需要的朋友可以参考下
    2022-12-12
  • 详解Spring @Lazy注解为什么能破解死循环

    详解Spring @Lazy注解为什么能破解死循环

    这篇文章主要来和大家探讨一下Spring中的@Lazy注解为什么能破解死循环,文中的示例代码讲解详细,具有一定的参考价值,需要的可以了解一下
    2023-07-07
  • mybatis plus中如何编写sql语句

    mybatis plus中如何编写sql语句

    这篇文章主要介绍了mybatis plus中如何编写sql语句,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Java中类的初始化和实例化区别详解

    Java中类的初始化和实例化区别详解

    这篇文章主要介绍了Java中类的初始化和实例化区别详解,类的初始化<BR>是完成程序执行前的准备工作,类的实例化(实例化对象)是指创建一个对象的过程,需要的朋友可以参考下
    2023-08-08

最新评论