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

网友投稿 306 2023-01-23


因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));

}

运行结http://果:

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 * from tb_score where 1=1

and score>#{score}

...

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 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

总结

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

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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:spring cloud Hystrix断路器的使用(熔断器)
下一篇:包含云平台接口自动化测试系统的词条
相关文章

 发表评论

暂时没有评论,来抢沙发吧~